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