PluginProbe ʕ •ᴥ•ʔ
Independent Analytics – WordPress Analytics Plugin / 2.9.2
Independent Analytics – WordPress Analytics Plugin v2.9.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 2 years 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 Public_API 1 year ago Rows 1 year ago Statistics 1 year ago Tables 1 year ago Utils 1 year ago Campaign_Builder.php 1 year ago Capability_Manager.php 1 year ago Chart.php 1 year ago Chart_Geo.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 2 years ago Empty_Report_Option.php 2 years ago Env.php 1 year ago Filters.php 1 year ago Geo_Database_Background_Job.php 2 years ago Geo_Database_Manager.php 1 year ago Geoposition.php 2 years 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 2 years 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 2 years 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 Visitors_Over_Time_Finder.php 2 years ago WP_Option_Cache_Bust.php 2 years ago
REST_API.php
378 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 /** @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 }
28 public function echo_tracking_script()
29 {
30 \IAWP\Migrations\Migrations::handle_migration_18_error();
31 \IAWP\Migrations\Migrations::handle_migration_22_error();
32 \IAWP\Migrations\Migrations::handle_migration_29_error();
33 \IAWP\Migrations\Migrations::create_or_migrate();
34 if (\IAWP\Migrations\Migrations::is_migrating()) {
35 return;
36 }
37 if (!\get_option('iawp_track_authenticated_users') && \is_user_logged_in()) {
38 // Todo - Can we clear the cache plugins to make sure that the pages so tracking code as user logs in & out?
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 (function () {
71 const isMatchingClass = (linkRule, href, classes) => {
72 return classes.includes(linkRule.value)
73 }
74 const isMatchingDomain = (linkRule, href, classes) => {
75 if(!URL.canParse(href)) {
76 return false
77 }
78
79 const url = new URL(href)
80
81 return linkRule.value === url.host
82 }
83 const isMatchingExtension = (linkRule, href, classes) => {
84 if(!URL.canParse(href)) {
85 return false
86 }
87
88 const url = new URL(href)
89
90 return url.pathname.endsWith('.' + linkRule.value)
91 }
92 const isMatchingSubdirectory = (linkRule, href, classes) => {
93 if(!URL.canParse(href)) {
94 return false
95 }
96
97 const url = new URL(href)
98
99 return url.pathname.startsWith('/' + linkRule.value + '/')
100 }
101 const isMatchingProtocol = (linkRule, href, classes) => {
102 if(!URL.canParse(href)) {
103 return false
104 }
105
106 const url = new URL(href)
107
108 return url.protocol === linkRule.value + ':'
109 }
110 const isMatch = (linkRule, href, classes) => {
111 switch (linkRule.type) {
112 case 'class':
113 return isMatchingClass(linkRule, href, classes)
114 case 'domain':
115 return isMatchingDomain(linkRule, href, classes)
116 case 'extension':
117 return isMatchingExtension(linkRule, href, classes)
118 case 'subdirectory':
119 return isMatchingSubdirectory(linkRule, href, classes)
120 case 'protocol':
121 return isMatchingProtocol(linkRule, href, classes)
122 default:
123 return false;
124 }
125 }
126 const track = (element) => {
127 const href = element.href ?? null
128 const classes = Array.from(element.classList)
129 const parentsToMatch = [
130 'li.menu-item', // Non-block menu item
131 'li.wp-block-navigation-item', // Block menu item
132 'div.wp-block-button', // Block button group
133 ]
134
135 // When a WordPress menu or button allows a user customizable class, the class is
136 // placed on a parent element instead of the button/a. If the parent matches one
137 // of these elements, then it's classes should also be considered.
138 if(parentsToMatch.some((toMatch) => element.parentElement.matches(toMatch))) {
139 classes.push(...Array.from(element.parentElement.classList))
140 }
141
142 const linkRules = <?php
143 echo $link_rules_json;
144 ?>
145
146 if(linkRules.length === 0) {
147 return
148 }
149
150 const hasMatch = linkRules.some((linkRule) => {
151 return isMatch(linkRule, href, classes)
152 })
153
154 if(!hasMatch) {
155 return
156 } else {
157
158 }
159
160 const url = "<?php
161 echo $track_click_url;
162 ?>";
163 const body = {
164 href: href,
165 classes: classes.join(' '),
166 ...<?php
167 echo \json_encode($data);
168 ?>
169 };
170
171 if (navigator.sendBeacon) {
172 let blob = new Blob([JSON.stringify(body)], {
173 type: "application/json"
174 });
175 navigator.sendBeacon(url, blob);
176 } else {
177 const xhr = new XMLHttpRequest();
178 xhr.open("POST", url, true);
179 xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
180 xhr.send(JSON.stringify(body))
181 }
182 }
183 document.addEventListener('mousedown', function (event) {
184 <?php
185 if (!\defined('IAWP_TESTING')) {
186 ?>
187 if (navigator.webdriver || /bot|crawler|spider|crawling|semrushbot|chrome-lighthouse/i.test(navigator.userAgent)) {
188 return;
189 }
190 <?php
191 }
192 ?>
193
194 const element = event.target.closest('a')
195
196 if(!element) {
197 return
198 }
199
200 const isPro = <?php
201 echo \IAWPSCOPED\iawp_is_pro() ? 'true' : 'false';
202 ?>
203
204 if(!isPro) {
205 return
206 }
207
208 // Don't track left clicks with this event. The click event is used for that.
209 if(event.button === 0) {
210 return
211 }
212
213 track(element)
214 })
215 document.addEventListener('click', function (event) {
216 <?php
217 if (!\defined('IAWP_TESTING')) {
218 ?>
219 if (navigator.webdriver || /bot|crawler|spider|crawling|semrushbot|chrome-lighthouse/i.test(navigator.userAgent)) {
220 return;
221 }
222 <?php
223 }
224 ?>
225
226 const element = event.target.closest('a, button')
227
228 if(!element) {
229 return
230 }
231
232 const isPro = <?php
233 echo \IAWPSCOPED\iawp_is_pro() ? 'true' : 'false';
234 ?>
235
236 if(!isPro) {
237 return
238 }
239
240 track(element)
241 })
242 document.addEventListener("DOMContentLoaded", function (e) {
243 if (document.hasOwnProperty("visibilityState") && document.visibilityState === "prerender") {
244 return;
245 }
246
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 let referrer_url = null;
258
259 if (typeof document.referrer === 'string' && document.referrer.length > 0) {
260 referrer_url = document.referrer;
261 }
262
263 const params = location.search.slice(1).split('&').reduce((acc, s) => {
264 const [k, v] = s.split('=');
265 return Object.assign(acc, {[k]: v});
266 }, {});
267
268 const url = "<?php
269 echo $track_view_url;
270 ?>";
271 const body = {
272 referrer_url,
273 utm_source: params.utm_source,
274 utm_medium: params.utm_medium,
275 utm_campaign: params.utm_campaign,
276 utm_term: params.utm_term,
277 utm_content: params.utm_content,
278 gclid: params.gclid,
279 ...<?php
280 echo \json_encode($data);
281 ?>
282 };
283
284 if (navigator.sendBeacon) {
285 let blob = new Blob([JSON.stringify(body)], {
286 type: "application/json"
287 });
288 navigator.sendBeacon(url, blob);
289 } else {
290 const xhr = new XMLHttpRequest();
291 xhr.open("POST", url, true);
292 xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
293 xhr.send(JSON.stringify(body))
294 }
295 });
296 })();
297 </script>
298 <?php
299 }
300 public function register_rest_api()
301 {
302 \register_rest_route('iawp', '/search', ['methods' => 'POST', 'callback' => [$this, 'track_view'], 'permission_callback' => function () {
303 return \true;
304 }]);
305 }
306 public function track_view($request)
307 {
308 if (Device::getInstance()->is_bot() && !\defined('IAWP_TESTING')) {
309 return;
310 }
311 \IAWP\Migrations\Migrations::handle_migration_18_error();
312 \IAWP\Migrations\Migrations::handle_migration_22_error();
313 \IAWP\Migrations\Migrations::handle_migration_29_error();
314 \IAWP\Migrations\Migrations::create_or_migrate();
315 if (\IAWP\Migrations\Migrations::is_migrating()) {
316 return;
317 }
318 if ($this->maybe_block_ip_address()) {
319 return;
320 }
321 $visitor = Visitor::fetch_current_visitor();
322 $signature = \md5(Salt::request_payload_salt() . \json_encode($request['payload']));
323 $campaign = [];
324 if (\IAWPSCOPED\iawp_is_pro()) {
325 $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'])];
326 }
327 if ($signature == $request['signature']) {
328 new \IAWP\View($request['payload'], $this->calculate_referrer_url($request), $visitor, $campaign);
329 return new \WP_REST_Response(['success' => \true], 200, ['X-IAWP' => 'iawp']);
330 } else {
331 return new \WP_REST_Response(['success' => \false], 200, ['X-IAWP' => 'iawp']);
332 }
333 }
334 private function calculate_referrer_url($request) : ?string
335 {
336 $referrer_url = $request['referrer_url'];
337 $url = new URL($referrer_url ?? '');
338 if (!\is_null($this->decode_or_nullify($request['gclid'])) && $url->get_domain() !== 'googleads.g.doubleclick.net') {
339 $referrer_url = 'https://googleads.iawp';
340 }
341 if (\is_null($referrer_url)) {
342 return null;
343 }
344 return $referrer_url;
345 }
346 private function decode_or_nullify($string)
347 {
348 if (!isset($string)) {
349 return null;
350 }
351 $safe_string = \trim(\urldecode($string));
352 $safe_string = \str_replace('+', ' ', $safe_string);
353 $safe_string = Security::string($safe_string);
354 if (\strlen($safe_string) === 0) {
355 return null;
356 }
357 return $safe_string;
358 }
359 private function maybe_block_ip_address()
360 {
361 $blocked_ips = \IAWPSCOPED\iawp()->get_option('iawp_blocked_ips', []);
362 if (\count($blocked_ips) == 0) {
363 return \false;
364 }
365 return Request::is_ip_address_blocked($blocked_ips);
366 }
367 private function block_user_role() : bool
368 {
369 $blocked_roles = \IAWPSCOPED\iawp()->get_option('iawp_blocked_roles', ['administrator']);
370 foreach (\wp_get_current_user()->roles as $visitor_role) {
371 if (\in_array($visitor_role, $blocked_roles)) {
372 return \true;
373 }
374 }
375 return \false;
376 }
377 }
378