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