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