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