PluginProbe ʕ •ᴥ•ʔ
Independent Analytics – WordPress Analytics Plugin / 2.11.9
Independent Analytics – WordPress Analytics Plugin v2.11.9
2.15.5 2.15.4 2.15.3 2.15.2 2.15.1 2.15.0 2.14.10 trunk 1.1 1.10 1.10.1 1.11 1.12 1.13 1.14 1.15 1.16 1.17 1.17.1 1.17.2 1.17.3 1.17.4 1.18 1.18.1 1.19.0 1.19.1 1.2 1.20.0 1.21.0 1.22.0 1.22.1 1.23.0 1.23.1 1.24.0 1.24.1 1.25.0 1.25.1 1.26.0 1.27.0 1.28.0 1.28.1 1.28.2 1.28.3 1.29.0 1.3 1.30.0 1.30.1 1.4 1.5 1.6 1.7 1.8 1.9 2.0.0 2.0.1 2.1.4 2.1.5 2.1.6 2.10.0 2.10.1 2.10.2 2.10.3 2.10.4 2.11.0 2.11.1 2.11.10 2.11.2 2.11.3 2.11.4 2.11.5 2.11.6 2.11.7 2.11.8 2.11.9 2.12.0 2.12.1 2.12.2 2.13.1 2.13.2 2.13.5 2.13.6 2.14.0 2.14.1 2.14.2 2.14.4 2.14.6 2.14.7 2.14.8 2.14.9 2.2.0 2.2.1 2.3.1 2.3.2 2.4.2 2.4.3 2.5.0 2.5.1 2.6.0 2.6.1 2.6.2 2.6.3 2.6.4 2.7.0 2.7.1 2.7.2 2.7.3 2.8.2 2.8.3 2.8.4 2.8.5 2.8.6 2.8.7 2.8.8 2.8.9 2.9.2 2.9.3 2.9.4 2.9.5 2.9.6 2.9.7
independent-analytics / IAWP / REST_API.php
independent-analytics / IAWP Last commit date
AJAX 1 year ago Admin_Page 1 year ago Click_Tracking 1 year ago Custom_WordPress_Columns 1 year ago Data_Pruning 1 year ago Date_Picker 1 year ago Date_Range 1 year ago Ecommerce 1 year ago Email_Reports 1 year ago Filter_Lists 1 year ago Form_Submissions 1 year ago Integrations 1 year ago Interval 1 year ago Menu_Bar_Stats 1 year ago Migrations 1 year ago Models 1 year ago Overview 1 year ago Public_API 1 year ago Rows 1 year ago Statistics 1 year ago Tables 1 year ago Utils 1 year ago Appearance.php 1 year ago Campaign_Builder.php 1 year ago Capability_Manager.php 1 year ago Chart.php 1 year ago Chart_Data.php 1 year ago Click_Tracking.php 1 year ago Cron_Job.php 1 year ago Cron_Manager.php 1 year ago Current_Traffic_Finder.php 1 year ago Dashboard_Options.php 1 year ago Dashboard_Widget.php 2 years ago Database.php 1 year ago Database_Manager.php 1 year ago Empty_Report_Option.php 2 years ago Env.php 1 year ago Filters.php 1 year ago Geo_Database_Background_Job.php 1 year ago Geo_Database_Manager.php 1 year ago Geoposition.php 1 year ago Icon_Directory.php 2 years ago Icon_Directory_Factory.php 2 years ago Illuminate_Builder.php 1 year ago Independent_Analytics.php 1 year ago Interrupt.php 1 year ago Known_Referrers.php 1 year ago MainWP.php 1 year ago Map.php 1 year ago Map_Data.php 1 year ago Migration_Fixer_Job.php 1 year ago Patch.php 1 year ago Payload_Validator.php 1 year ago Plugin_Conflict_Detector.php 1 year ago Plugin_Group.php 1 year ago Plugin_Group_Option.php 2 years ago Query.php 1 year ago Query_Taps.php 1 year ago Quick_Stats.php 1 year ago REST_API.php 1 year ago Real_Time.php 1 year ago Report.php 1 year ago Report_Finder.php 1 year ago Report_Options_Parser.php 2 years ago Resource_Identifier.php 1 year ago Settings.php 1 year ago Sort_Configuration.php 1 year ago Tables.php 1 year ago Track_Resource_Changes.php 1 year ago View.php 1 year ago View_Counter.php 1 year ago Views_Over_Time_Finder.php 1 year ago WP_Option_Cache_Bust.php 2 years ago
REST_API.php
466 lines
1 <?php
2
3 namespace IAWP;
4
5 use IAWP\Click_Tracking\Link_Rule_Finder;
6 use IAWP\Models\Visitor;
7 use IAWP\Utils\Device;
8 use IAWP\Utils\Request;
9 use IAWP\Utils\Salt;
10 use IAWP\Utils\Security;
11 use IAWP\Utils\URL;
12 use IAWPSCOPED\Illuminate\Support\Str;
13 /** @internal */
14 class REST_API
15 {
16 public function __construct()
17 {
18 \add_action('wp_footer', [$this, 'echo_tracking_script']);
19 \add_action('rest_api_init', [$this, 'register_rest_api']);
20 // Support for PDF Viewer by Themencode (free and pro versions)
21 \add_action('tnc_pvfw_viewer_head', [$this, 'echo_tracking_script']);
22 \add_action('tnc_pvfw_head', [$this, 'echo_tracking_script']);
23 // Support for Coming Soon and Maintenance by Colorlib
24 \add_action('ccsm_header', [$this, 'echo_tracking_script']);
25 // Support for CMP - Coming Soon & Maintenance
26 \add_action('cmp_footer', [$this, 'echo_tracking_script']);
27 }
28 public function echo_tracking_script()
29 {
30 \IAWP\Migrations\Migrations::handle_migration_18_error();
31 \IAWP\Migrations\Migrations::handle_migration_22_error();
32 \IAWP\Migrations\Migrations::handle_migration_29_error();
33 \IAWP\Migrations\Migrations::create_or_migrate();
34 if (\IAWP\Migrations\Migrations::is_migrating()) {
35 return;
36 }
37 if (!\get_option('iawp_track_authenticated_users') && \is_user_logged_in()) {
38 return;
39 }
40 if ($this->block_user_role()) {
41 return;
42 }
43 // Don't track post or page previews
44 if (\is_preview()) {
45 return;
46 }
47 // Don't track the Thrive Leads form builder
48 if (\array_key_exists('tve', $_GET)) {
49 return;
50 }
51 $payload = [];
52 $current_resource = \IAWP\Resource_Identifier::for_resource_being_viewed();
53 if (\is_null($current_resource)) {
54 return;
55 }
56 $payload['resource'] = $current_resource->type();
57 if ($current_resource->has_meta()) {
58 $payload[$current_resource->meta_key()] = $current_resource->meta_value();
59 }
60 $payload['page'] = \max(1, \get_query_var('paged'));
61 $data = ['payload' => $payload];
62 $data['signature'] = \md5(Salt::request_payload_salt() . \json_encode($data['payload']));
63 $track_view_url = \get_rest_url(null, '/iawp/search');
64 $track_click_url = \IAWPSCOPED\iawp_url_to('/iawp-click-endpoint.php');
65 $link_rules_json = \json_encode(Link_Rule_Finder::cached_link_rules());
66 ?>
67 <script>
68 // Do not change this comment line otherwise Speed Optimizer won't be able to detect this script
69
70 (function () {
71 const calculateParentDistance = (child, parent) => {
72 let count = 0;
73 let currentElement = child;
74
75 // Traverse up the DOM tree until we reach parent or the top of the DOM
76 while (currentElement && currentElement !== parent) {
77 currentElement = currentElement.parentNode;
78 count++;
79 }
80
81 // If parent was not found in the hierarchy, return -1
82 if (!currentElement) {
83 return -1; // Indicates parent is not an ancestor of element
84 }
85
86 return count; // Number of layers between element and parent
87 }
88 const isMatchingClass = (linkRule, href, classes, ids) => {
89 return classes.includes(linkRule.value)
90 }
91 const isMatchingId = (linkRule, href, classes, ids) => {
92 return ids.includes(linkRule.value)
93 }
94 const isMatchingDomain = (linkRule, href, classes, ids) => {
95 if(!URL.canParse(href)) {
96 return false
97 }
98
99 const url = new URL(href)
100
101 return linkRule.value === url.host
102 }
103 const isMatchingExtension = (linkRule, href, classes, ids) => {
104 if(!URL.canParse(href)) {
105 return false
106 }
107
108 const url = new URL(href)
109
110 return url.pathname.endsWith('.' + linkRule.value)
111 }
112 const isMatchingSubdirectory = (linkRule, href, classes, ids) => {
113 if(!URL.canParse(href)) {
114 return false
115 }
116
117 const url = new URL(href)
118
119 return url.pathname.startsWith('/' + linkRule.value + '/')
120 }
121 const isMatchingProtocol = (linkRule, href, classes, ids) => {
122 if(!URL.canParse(href)) {
123 return false
124 }
125
126 const url = new URL(href)
127
128 return url.protocol === linkRule.value + ':'
129 }
130 const isMatchingExternal = (linkRule, href, classes, ids) => {
131 if(!URL.canParse(href) || !URL.canParse(document.location.href)) {
132 return false
133 }
134
135 const matchingProtocols = ['http:', 'https:']
136 const siteUrl = new URL(document.location.href)
137 const linkUrl = new URL(href)
138
139 // Links to subdomains will appear to be external matches according to JavaScript,
140 // but the PHP rules will filter those events out.
141 return matchingProtocols.includes(linkUrl.protocol) && siteUrl.host !== linkUrl.host
142 }
143 const isMatch = (linkRule, href, classes, ids) => {
144 switch (linkRule.type) {
145 case 'class':
146 return isMatchingClass(linkRule, href, classes, ids)
147 case 'id':
148 return isMatchingId(linkRule, href, classes, ids)
149 case 'domain':
150 return isMatchingDomain(linkRule, href, classes, ids)
151 case 'extension':
152 return isMatchingExtension(linkRule, href, classes, ids)
153 case 'subdirectory':
154 return isMatchingSubdirectory(linkRule, href, classes, ids)
155 case 'protocol':
156 return isMatchingProtocol(linkRule, href, classes, ids)
157 case 'external':
158 return isMatchingExternal(linkRule, href, classes, ids)
159 default:
160 return false;
161 }
162 }
163 const track = (element) => {
164 const href = element.href ?? null
165 const classes = Array.from(element.classList)
166 const ids = [element.id]
167 const linkRules = <?php
168 echo $link_rules_json;
169 ?>
170
171 if(linkRules.length === 0) {
172 return
173 }
174
175 // For link rules that target an id, we need to allow that id to appear
176 // in any ancestor up to the 7th ancestor. This loop looks for those matches
177 // and counts them.
178 linkRules.forEach((linkRule) => {
179 if(linkRule.type !== 'id') {
180 return;
181 }
182
183 const matchingAncestor = element.closest('#' + linkRule.value)
184
185 if(!matchingAncestor || matchingAncestor.matches('html, body')) {
186 return;
187 }
188
189 const depth = calculateParentDistance(element, matchingAncestor)
190
191 if(depth < 7) {
192 ids.push(linkRule.value)
193 }
194 });
195
196 // For link rules that target a class, we need to allow that class to appear
197 // in any ancestor up to the 7th ancestor. This loop looks for those matches
198 // and counts them.
199 linkRules.forEach((linkRule) => {
200 if(linkRule.type !== 'class') {
201 return;
202 }
203
204 const matchingAncestor = element.closest('.' + linkRule.value)
205
206 if(!matchingAncestor || matchingAncestor.matches('html, body')) {
207 return;
208 }
209
210 const depth = calculateParentDistance(element, matchingAncestor)
211
212 if(depth < 7) {
213 classes.push(linkRule.value)
214 }
215 });
216
217 const hasMatch = linkRules.some((linkRule) => {
218 return isMatch(linkRule, href, classes, ids)
219 })
220
221 if(!hasMatch) {
222 return
223 }
224
225 const url = "<?php
226 echo $track_click_url;
227 ?>";
228 const body = {
229 href: href,
230 classes: classes.join(' '),
231 ids: ids.join(' '),
232 ...<?php
233 echo \json_encode($data);
234 ?>
235 };
236
237 if (navigator.sendBeacon) {
238 let blob = new Blob([JSON.stringify(body)], {
239 type: "application/json"
240 });
241 navigator.sendBeacon(url, blob);
242 } else {
243 const xhr = new XMLHttpRequest();
244 xhr.open("POST", url, true);
245 xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
246 xhr.send(JSON.stringify(body))
247 }
248 }
249 document.addEventListener('mousedown', function (event) {
250 <?php
251 if (!\defined('IAWP_TESTING')) {
252 ?>
253 if (navigator.webdriver || /bot|crawler|spider|crawling|semrushbot|chrome-lighthouse/i.test(navigator.userAgent)) {
254 return;
255 }
256 <?php
257 }
258 ?>
259
260 const element = event.target.closest('a')
261
262 if(!element) {
263 return
264 }
265
266 const isPro = <?php
267 echo \IAWPSCOPED\iawp_is_pro() ? 'true' : 'false';
268 ?>
269
270 if(!isPro) {
271 return
272 }
273
274 // Don't track left clicks with this event. The click event is used for that.
275 if(event.button === 0) {
276 return
277 }
278
279 track(element)
280 })
281 document.addEventListener('click', function (event) {
282 <?php
283 if (!\defined('IAWP_TESTING')) {
284 ?>
285 if (navigator.webdriver || /bot|crawler|spider|crawling|semrushbot|chrome-lighthouse/i.test(navigator.userAgent)) {
286 return;
287 }
288 <?php
289 }
290 ?>
291
292 const element = event.target.closest('a, button, input[type="submit"], input[type="button"]')
293
294 if(!element) {
295 return
296 }
297
298 const isPro = <?php
299 echo \IAWPSCOPED\iawp_is_pro() ? 'true' : 'false';
300 ?>
301
302 if(!isPro) {
303 return
304 }
305
306 track(element)
307 })
308 document.addEventListener('play', function (event) {
309 <?php
310 if (!\defined('IAWP_TESTING')) {
311 ?>
312 if (navigator.webdriver || /bot|crawler|spider|crawling|semrushbot|chrome-lighthouse/i.test(navigator.userAgent)) {
313 return;
314 }
315 <?php
316 }
317 ?>
318
319 const element = event.target.closest('audio, video')
320
321 if(!element) {
322 return
323 }
324
325 const isPro = <?php
326 echo \IAWPSCOPED\iawp_is_pro() ? 'true' : 'false';
327 ?>
328
329 if(!isPro) {
330 return
331 }
332
333 track(element)
334 }, true)
335 document.addEventListener("DOMContentLoaded", function (e) {
336 if (document.hasOwnProperty("visibilityState") && document.visibilityState === "prerender") {
337 return;
338 }
339
340 <?php
341 if (!\defined('IAWP_TESTING')) {
342 ?>
343 if (navigator.webdriver || /bot|crawler|spider|crawling|semrushbot|chrome-lighthouse/i.test(navigator.userAgent)) {
344 return;
345 }
346 <?php
347 }
348 ?>
349
350 let referrer_url = null;
351
352 if (typeof document.referrer === 'string' && document.referrer.length > 0) {
353 referrer_url = document.referrer;
354 }
355
356 const params = location.search.slice(1).split('&').reduce((acc, s) => {
357 const [k, v] = s.split('=');
358 return Object.assign(acc, {[k]: v});
359 }, {});
360
361 const url = "<?php
362 echo $track_view_url;
363 ?>";
364 const body = {
365 referrer_url,
366 utm_source: params.utm_source,
367 utm_medium: params.utm_medium,
368 utm_campaign: params.utm_campaign,
369 utm_term: params.utm_term,
370 utm_content: params.utm_content,
371 gclid: params.gclid,
372 ...<?php
373 echo \json_encode($data);
374 ?>
375 };
376
377 if (navigator.sendBeacon) {
378 let blob = new Blob([JSON.stringify(body)], {
379 type: "application/json"
380 });
381 navigator.sendBeacon(url, blob);
382 } else {
383 const xhr = new XMLHttpRequest();
384 xhr.open("POST", url, true);
385 xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
386 xhr.send(JSON.stringify(body))
387 }
388 });
389 })();
390 </script>
391 <?php
392 }
393 public function register_rest_api()
394 {
395 \register_rest_route('iawp', '/search', ['methods' => 'POST', 'callback' => [$this, 'track_view'], 'permission_callback' => function () {
396 return \true;
397 }]);
398 }
399 public function track_view($request)
400 {
401 if (Device::getInstance()->is_bot() && !\defined('IAWP_TESTING')) {
402 return;
403 }
404 \IAWP\Migrations\Migrations::handle_migration_18_error();
405 \IAWP\Migrations\Migrations::handle_migration_22_error();
406 \IAWP\Migrations\Migrations::handle_migration_29_error();
407 \IAWP\Migrations\Migrations::create_or_migrate();
408 if (\IAWP\Migrations\Migrations::is_migrating()) {
409 return;
410 }
411 if (Request::is_ip_address_blocked()) {
412 return;
413 }
414 $visitor = Visitor::fetch_current_visitor();
415 $signature = \md5(Salt::request_payload_salt() . \json_encode($request['payload']));
416 $campaign = [];
417 if (\IAWPSCOPED\iawp_is_pro()) {
418 $campaign = ['utm_source' => $this->decode_or_nullify($request['utm_source']), 'utm_medium' => $this->decode_or_nullify($request['utm_medium']), 'utm_campaign' => $this->decode_or_nullify($request['utm_campaign']), 'utm_term' => $this->decode_or_nullify($request['utm_term']), 'utm_content' => $this->decode_or_nullify($request['utm_content'])];
419 }
420 if ($signature == $request['signature']) {
421 new \IAWP\View($request['payload'], $this->calculate_referrer_url($request), $visitor, $campaign);
422 return new \WP_REST_Response(['success' => \true], 200, ['X-IAWP' => 'iawp']);
423 } else {
424 return new \WP_REST_Response(['success' => \false], 200, ['X-IAWP' => 'iawp']);
425 }
426 }
427 private function calculate_referrer_url($request) : ?string
428 {
429 $referrer_url = $request['referrer_url'];
430 $url = new URL($referrer_url ?? '');
431 if (\is_string($this->decode_or_nullify($request['gclid'])) && $url->get_domain() !== 'googleads.g.doubleclick.net') {
432 $referrer_url = 'https://googleads.iawp';
433 }
434 if (\is_string($this->decode_or_nullify($request['fbclid'])) && ($url->get_domain() === 'facebook.com' || Str::endsWith($url->get_domain(), '.facebook.com'))) {
435 $referrer_url = 'https://facebookads.iawp';
436 }
437 if (\is_null($referrer_url)) {
438 return null;
439 }
440 return $referrer_url;
441 }
442 private function decode_or_nullify($string) : ?string
443 {
444 if (!isset($string)) {
445 return null;
446 }
447 $safe_string = \trim(\urldecode($string));
448 $safe_string = \str_replace('+', ' ', $safe_string);
449 $safe_string = Security::string($safe_string);
450 if (\strlen($safe_string) === 0) {
451 return null;
452 }
453 return $safe_string;
454 }
455 private function block_user_role() : bool
456 {
457 $blocked_roles = \IAWPSCOPED\iawp()->get_option('iawp_blocked_roles', ['administrator']);
458 foreach (\wp_get_current_user()->roles as $visitor_role) {
459 if (\in_array($visitor_role, $blocked_roles)) {
460 return \true;
461 }
462 }
463 return \false;
464 }
465 }
466