PluginProbe ʕ •ᴥ•ʔ
Independent Analytics – WordPress Analytics Plugin / 2.12.2
Independent Analytics – WordPress Analytics Plugin v2.12.2
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 10 months ago Admin_Page 11 months ago Click_Tracking 1 year ago Custom_WordPress_Columns 1 year ago Data_Pruning 1 year ago Date_Picker 1 year ago Date_Range 11 months ago Ecommerce 1 year ago Email_Reports 11 months ago Examiner 11 months ago Filter_Lists 1 year ago Form_Submissions 11 months ago Integrations 1 year ago Interval 1 year ago Menu_Bar_Stats 1 year ago Migrations 1 year ago Models 10 months ago Overview 11 months ago Public_API 1 year ago Rows 10 months ago Statistics 11 months ago Tables 11 months ago Utils 11 months 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 11 months ago Dashboard_Widget.php 2 years ago Database.php 1 year ago Database_Manager.php 11 months ago Empty_Report_Option.php 2 years ago Env.php 1 year ago Examiner_Config.php 11 months ago Filters.php 1 year ago Geo_Database_Background_Job.php 1 year ago Geo_Database_Manager.php 11 months ago Geoposition.php 1 year ago Icon_Directory.php 2 years ago Icon_Directory_Factory.php 2 years ago Illuminate_Builder.php 10 months ago Independent_Analytics.php 11 months ago Interrupt.php 1 year ago Known_Referrers.php 11 months 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 11 months ago Plugin_Group.php 1 year ago Plugin_Group_Option.php 2 years ago Query.php 1 year ago Query_Taps.php 11 months ago Quick_Stats.php 11 months ago REST_API.php 11 months ago Real_Time.php 1 year ago Report.php 1 year ago Report_Finder.php 1 year ago Report_Options_Parser.php 1 year ago Resource_Identifier.php 1 year ago Settings.php 11 months ago Sort_Configuration.php 1 year ago Tables.php 1 year ago Track_Resource_Changes.php 1 year ago View.php 11 months 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
461 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 // Support for Maintenance plugin
28 \add_action('add_gg_analytics_code', [$this, 'echo_tracking_script']);
29 }
30 public function echo_tracking_script()
31 {
32 \IAWP\Migrations\Migrations::handle_migration_18_error();
33 \IAWP\Migrations\Migrations::handle_migration_22_error();
34 \IAWP\Migrations\Migrations::handle_migration_29_error();
35 \IAWP\Migrations\Migrations::create_or_migrate();
36 if (\IAWP\Migrations\Migrations::is_migrating()) {
37 return;
38 }
39 if (!\get_option('iawp_track_authenticated_users') && \is_user_logged_in()) {
40 return;
41 }
42 if (Request::is_blocked_user_role()) {
43 return;
44 }
45 if (isset($_COOKIE['iawp_ignore_visitor'])) {
46 return;
47 }
48 // Don't track post or page previews
49 if (\is_preview()) {
50 return;
51 }
52 // Don't track the Thrive Leads form builder
53 if (\array_key_exists('tve', $_GET)) {
54 return;
55 }
56 $payload = [];
57 $current_resource = \IAWP\Resource_Identifier::for_resource_being_viewed();
58 if (\is_null($current_resource)) {
59 return;
60 }
61 $payload['resource'] = $current_resource->type();
62 if ($current_resource->has_meta()) {
63 $payload[$current_resource->meta_key()] = $current_resource->meta_value();
64 }
65 $payload['page'] = \max(1, \get_query_var('paged'));
66 $data = ['payload' => $payload];
67 $data['signature'] = \md5(Salt::request_payload_salt() . \json_encode($data['payload']));
68 $track_view_url = \get_rest_url(null, '/iawp/search');
69 $track_click_url = \IAWPSCOPED\iawp_url_to('/iawp-click-endpoint.php');
70 $link_rules_json = \json_encode(Link_Rule_Finder::cached_link_rules());
71 ?>
72 <script>
73 // Do not change this comment line otherwise Speed Optimizer won't be able to detect this script
74
75 (function () {
76 const calculateParentDistance = (child, parent) => {
77 let count = 0;
78 let currentElement = child;
79
80 // Traverse up the DOM tree until we reach parent or the top of the DOM
81 while (currentElement && currentElement !== parent) {
82 currentElement = currentElement.parentNode;
83 count++;
84 }
85
86 // If parent was not found in the hierarchy, return -1
87 if (!currentElement) {
88 return -1; // Indicates parent is not an ancestor of element
89 }
90
91 return count; // Number of layers between element and parent
92 }
93 const isMatchingClass = (linkRule, href, classes, ids) => {
94 return classes.includes(linkRule.value)
95 }
96 const isMatchingId = (linkRule, href, classes, ids) => {
97 return ids.includes(linkRule.value)
98 }
99 const isMatchingDomain = (linkRule, href, classes, ids) => {
100 if(!URL.canParse(href)) {
101 return false
102 }
103
104 const url = new URL(href)
105
106 return linkRule.value === url.host
107 }
108 const isMatchingExtension = (linkRule, href, classes, ids) => {
109 if(!URL.canParse(href)) {
110 return false
111 }
112
113 const url = new URL(href)
114
115 return url.pathname.endsWith('.' + linkRule.value)
116 }
117 const isMatchingSubdirectory = (linkRule, href, classes, ids) => {
118 if(!URL.canParse(href)) {
119 return false
120 }
121
122 const url = new URL(href)
123
124 return url.pathname.startsWith('/' + linkRule.value + '/')
125 }
126 const isMatchingProtocol = (linkRule, href, classes, ids) => {
127 if(!URL.canParse(href)) {
128 return false
129 }
130
131 const url = new URL(href)
132
133 return url.protocol === linkRule.value + ':'
134 }
135 const isMatchingExternal = (linkRule, href, classes, ids) => {
136 if(!URL.canParse(href) || !URL.canParse(document.location.href)) {
137 return false
138 }
139
140 const matchingProtocols = ['http:', 'https:']
141 const siteUrl = new URL(document.location.href)
142 const linkUrl = new URL(href)
143
144 // Links to subdomains will appear to be external matches according to JavaScript,
145 // but the PHP rules will filter those events out.
146 return matchingProtocols.includes(linkUrl.protocol) && siteUrl.host !== linkUrl.host
147 }
148 const isMatch = (linkRule, href, classes, ids) => {
149 switch (linkRule.type) {
150 case 'class':
151 return isMatchingClass(linkRule, href, classes, ids)
152 case 'id':
153 return isMatchingId(linkRule, href, classes, ids)
154 case 'domain':
155 return isMatchingDomain(linkRule, href, classes, ids)
156 case 'extension':
157 return isMatchingExtension(linkRule, href, classes, ids)
158 case 'subdirectory':
159 return isMatchingSubdirectory(linkRule, href, classes, ids)
160 case 'protocol':
161 return isMatchingProtocol(linkRule, href, classes, ids)
162 case 'external':
163 return isMatchingExternal(linkRule, href, classes, ids)
164 default:
165 return false;
166 }
167 }
168 const track = (element) => {
169 const href = element.href ?? null
170 const classes = Array.from(element.classList)
171 const ids = [element.id]
172 const linkRules = <?php
173 echo $link_rules_json;
174 ?>
175
176 if(linkRules.length === 0) {
177 return
178 }
179
180 // For link rules that target an id, we need to allow that id to appear
181 // in any ancestor up to the 7th ancestor. This loop looks for those matches
182 // and counts them.
183 linkRules.forEach((linkRule) => {
184 if(linkRule.type !== 'id') {
185 return;
186 }
187
188 const matchingAncestor = element.closest('#' + linkRule.value)
189
190 if(!matchingAncestor || matchingAncestor.matches('html, body')) {
191 return;
192 }
193
194 const depth = calculateParentDistance(element, matchingAncestor)
195
196 if(depth < 7) {
197 ids.push(linkRule.value)
198 }
199 });
200
201 // For link rules that target a class, we need to allow that class to appear
202 // in any ancestor up to the 7th ancestor. This loop looks for those matches
203 // and counts them.
204 linkRules.forEach((linkRule) => {
205 if(linkRule.type !== 'class') {
206 return;
207 }
208
209 const matchingAncestor = element.closest('.' + linkRule.value)
210
211 if(!matchingAncestor || matchingAncestor.matches('html, body')) {
212 return;
213 }
214
215 const depth = calculateParentDistance(element, matchingAncestor)
216
217 if(depth < 7) {
218 classes.push(linkRule.value)
219 }
220 });
221
222 const hasMatch = linkRules.some((linkRule) => {
223 return isMatch(linkRule, href, classes, ids)
224 })
225
226 if(!hasMatch) {
227 return
228 }
229
230 const url = "<?php
231 echo $track_click_url;
232 ?>";
233 const body = {
234 href: href,
235 classes: classes.join(' '),
236 ids: ids.join(' '),
237 ...<?php
238 echo \json_encode($data);
239 ?>
240 };
241
242 if (navigator.sendBeacon) {
243 let blob = new Blob([JSON.stringify(body)], {
244 type: "application/json"
245 });
246 navigator.sendBeacon(url, blob);
247 } else {
248 const xhr = new XMLHttpRequest();
249 xhr.open("POST", url, true);
250 xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
251 xhr.send(JSON.stringify(body))
252 }
253 }
254 document.addEventListener('mousedown', function (event) {
255 <?php
256 if (!\defined('IAWP_TESTING')) {
257 ?>
258 if (navigator.webdriver || /bot|crawler|spider|crawling|semrushbot|chrome-lighthouse/i.test(navigator.userAgent)) {
259 return;
260 }
261 <?php
262 }
263 ?>
264
265 const element = event.target.closest('a')
266
267 if(!element) {
268 return
269 }
270
271 const isPro = <?php
272 echo \IAWPSCOPED\iawp_is_pro() ? 'true' : 'false';
273 ?>
274
275 if(!isPro) {
276 return
277 }
278
279 // Don't track left clicks with this event. The click event is used for that.
280 if(event.button === 0) {
281 return
282 }
283
284 track(element)
285 })
286 document.addEventListener('click', function (event) {
287 <?php
288 if (!\defined('IAWP_TESTING')) {
289 ?>
290 if (navigator.webdriver || /bot|crawler|spider|crawling|semrushbot|chrome-lighthouse/i.test(navigator.userAgent)) {
291 return;
292 }
293 <?php
294 }
295 ?>
296
297 const element = event.target.closest('a, button, input[type="submit"], input[type="button"]')
298
299 if(!element) {
300 return
301 }
302
303 const isPro = <?php
304 echo \IAWPSCOPED\iawp_is_pro() ? 'true' : 'false';
305 ?>
306
307 if(!isPro) {
308 return
309 }
310
311 track(element)
312 })
313 document.addEventListener('play', function (event) {
314 <?php
315 if (!\defined('IAWP_TESTING')) {
316 ?>
317 if (navigator.webdriver || /bot|crawler|spider|crawling|semrushbot|chrome-lighthouse/i.test(navigator.userAgent)) {
318 return;
319 }
320 <?php
321 }
322 ?>
323
324 const element = event.target.closest('audio, video')
325
326 if(!element) {
327 return
328 }
329
330 const isPro = <?php
331 echo \IAWPSCOPED\iawp_is_pro() ? 'true' : 'false';
332 ?>
333
334 if(!isPro) {
335 return
336 }
337
338 track(element)
339 }, true)
340 document.addEventListener("DOMContentLoaded", function (e) {
341 if (document.hasOwnProperty("visibilityState") && document.visibilityState === "prerender") {
342 return;
343 }
344
345 <?php
346 if (!\defined('IAWP_TESTING')) {
347 ?>
348 if (navigator.webdriver || /bot|crawler|spider|crawling|semrushbot|chrome-lighthouse/i.test(navigator.userAgent)) {
349 return;
350 }
351 <?php
352 }
353 ?>
354
355 let referrer_url = null;
356
357 if (typeof document.referrer === 'string' && document.referrer.length > 0) {
358 referrer_url = document.referrer;
359 }
360
361 const params = location.search.slice(1).split('&').reduce((acc, s) => {
362 const [k, v] = s.split('=');
363 return Object.assign(acc, {[k]: v});
364 }, {});
365
366 const url = "<?php
367 echo $track_view_url;
368 ?>";
369 const body = {
370 referrer_url,
371 utm_source: params.utm_source,
372 utm_medium: params.utm_medium,
373 utm_campaign: params.utm_campaign,
374 utm_term: params.utm_term,
375 utm_content: params.utm_content,
376 gclid: params.gclid,
377 ...<?php
378 echo \json_encode($data);
379 ?>
380 };
381
382 if (navigator.sendBeacon) {
383 let blob = new Blob([JSON.stringify(body)], {
384 type: "application/json"
385 });
386 navigator.sendBeacon(url, blob);
387 } else {
388 const xhr = new XMLHttpRequest();
389 xhr.open("POST", url, true);
390 xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
391 xhr.send(JSON.stringify(body))
392 }
393 });
394 })();
395 </script>
396 <?php
397 }
398 public function register_rest_api()
399 {
400 \register_rest_route('iawp', '/search', ['methods' => 'POST', 'callback' => [$this, 'track_view'], 'permission_callback' => function () {
401 return \true;
402 }]);
403 }
404 public function track_view($request)
405 {
406 if (Device::getInstance()->is_bot() && !\defined('IAWP_TESTING')) {
407 return;
408 }
409 \IAWP\Migrations\Migrations::handle_migration_18_error();
410 \IAWP\Migrations\Migrations::handle_migration_22_error();
411 \IAWP\Migrations\Migrations::handle_migration_29_error();
412 \IAWP\Migrations\Migrations::create_or_migrate();
413 if (\IAWP\Migrations\Migrations::is_migrating()) {
414 return;
415 }
416 if (Request::is_ip_address_blocked()) {
417 return;
418 }
419 $visitor = Visitor::fetch_current_visitor();
420 $signature = \md5(Salt::request_payload_salt() . \json_encode($request['payload']));
421 $campaign = [];
422 if (\IAWPSCOPED\iawp_is_pro()) {
423 $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'])];
424 }
425 if ($signature == $request['signature']) {
426 new \IAWP\View($request['payload'], $this->calculate_referrer_url($request), $visitor, $campaign);
427 return new \WP_REST_Response(['success' => \true], 200, ['X-IAWP' => 'iawp']);
428 } else {
429 return new \WP_REST_Response(['success' => \false], 200, ['X-IAWP' => 'iawp']);
430 }
431 }
432 private function calculate_referrer_url($request) : ?string
433 {
434 $referrer_url = $request['referrer_url'];
435 $url = new URL($referrer_url ?? '');
436 if (\is_string($this->decode_or_nullify($request['gclid'])) && $url->get_domain() !== 'googleads.g.doubleclick.net') {
437 $referrer_url = 'https://googleads.iawp';
438 }
439 if (\is_string($this->decode_or_nullify($request['fbclid'])) && ($url->get_domain() === 'facebook.com' || Str::endsWith($url->get_domain(), '.facebook.com'))) {
440 $referrer_url = 'https://facebookads.iawp';
441 }
442 if (\is_null($referrer_url)) {
443 return null;
444 }
445 return $referrer_url;
446 }
447 private function decode_or_nullify($string) : ?string
448 {
449 if (!isset($string)) {
450 return null;
451 }
452 $safe_string = \trim(\urldecode($string));
453 $safe_string = \str_replace('+', ' ', $safe_string);
454 $safe_string = Security::string($safe_string);
455 if (\strlen($safe_string) === 0) {
456 return null;
457 }
458 return $safe_string;
459 }
460 }
461