PluginProbe ʕ •ᴥ•ʔ
EmbedPress – PDF Embedder, 3D PDF FlipBook, Google Reviews, YouTube Videos, Upload & Embed PDF documents / 4.4.7
EmbedPress – PDF Embedder, 3D PDF FlipBook, Google Reviews, YouTube Videos, Upload & Embed PDF documents v4.4.7
4.6.0 4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 trunk 1.0.0 1.1.0 1.1.1 1.1.2 1.1.3 1.2.0 1.3.0 1.3.1 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.5.0 1.6.0 1.6.1 1.6.2 1.6.3 1.7.0 1.7.1 1.7.2 1.7.3 1.7.4 1.7.5 2.0.0 2.0.1 2.0.2 2.0.3 2.1.0 2.1.1 2.1.2 2.1.3 2.1.4 2.1.5 2.1.6 2.2.0 2.2.1 2.2.2 2.3.0 2.3.1 2.3.2 2.3.3 2.4.0 2.4.1 2.5.0 2.5.1 2.5.2 2.5.3 2.5.4 2.5.5 2.6.0 2.6.1 2.6.2 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 3.0.0 3.0.1 3.0.2 3.0.3 3.0.4 3.1.0 3.1.1 3.1.2 3.1.3 3.2.0 3.2.1 3.3.0 3.3.1 3.3.2 3.3.3 3.3.4 3.3.5 3.3.6 3.3.7 3.4.0 3.4.1 3.4.2 3.4.3 3.5.0 3.5.1 3.5.2 3.5.3 3.6.0 3.6.1 3.6.2 3.6.3 3.6.4 3.6.5 3.6.6 3.6.7 3.6.8 3.7.0 3.7.1 3.7.2 3.7.3 3.8.0 3.8.1 3.8.2 3.8.3 3.8.4 3.8.5 3.9.0 3.9.1 3.9.10 3.9.11 3.9.12 3.9.13 3.9.14 3.9.15 3.9.16 3.9.17 3.9.2 3.9.3 3.9.4 3.9.5 3.9.6 3.9.7 3.9.8 3.9.9 4.0.0 4.0.1 4.0.10 4.0.11 4.0.12 4.0.13 4.0.14 4.0.2 4.0.3 4.0.4 4.0.5 4.0.6 4.0.7 4.0.8 4.0.9 4.1.0 4.1.1 4.1.10 4.1.2 4.1.3 4.1.4 4.1.5 4.1.6 4.1.7 4.1.8 4.1.9 4.2.0 4.2.1 4.2.2 4.2.3 4.2.4 4.2.5 4.2.6 4.2.7 4.2.8 4.2.9 4.3.0 4.3.1 4.4.0 4.4.1 4.4.10 4.4.11 4.4.2 4.4.3 4.4.4 4.4.5 4.4.6 4.4.7 4.4.8 4.4.9 4.5.0 4.5.1
embedpress / EmbedPress / Includes / Classes / Analytics / Pro_Data_Collector.php
embedpress / EmbedPress / Includes / Classes / Analytics Last commit date
Analytics_Manager.php 9 months ago Browser_Detector.php 9 months ago Content_Cache_Manager.php 9 months ago Data_Collector.php 7 months ago Email_Reports.php 9 months ago Export_Manager.php 9 months ago License_Manager.php 9 months ago Milestone_Manager.php 9 months ago Pro_Data_Collector.php 9 months ago REST_API.php 9 months ago
Pro_Data_Collector.php
701 lines
1 <?php
2
3 namespace EmbedPress\Includes\Classes\Analytics;
4
5 defined('ABSPATH') or die("No direct script access allowed.");
6
7 /**
8 * EmbedPress Pro Analytics Data Collector
9 *
10 * Handles all pro analytics features
11 *
12 * @package EmbedPress
13 * @author EmbedPress <help@embedpress.com>
14 * @copyright Copyright (C) 2023 WPDeveloper. All rights reserved.
15 * @license GPLv3 or later
16 * @since 4.2.7
17 */
18 class Pro_Data_Collector
19 {
20 /**
21 * Build date condition for SQL queries
22 *
23 * @param array $args
24 * @param string $date_column
25 * @return string
26 */
27 private function build_date_condition($args = [], $date_column = 'created_at')
28 {
29 global $wpdb;
30
31 $date_condition = '';
32
33 // Check if specific start_date and end_date are provided
34 if (!empty($args['start_date']) && !empty($args['end_date'])) {
35 $start_date = sanitize_text_field($args['start_date']);
36 $end_date = sanitize_text_field($args['end_date']);
37
38 // Validate date format (YYYY-MM-DD)
39 if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $start_date) && preg_match('/^\d{4}-\d{2}-\d{2}$/', $end_date)) {
40 $date_condition = $wpdb->prepare(
41 "AND DATE($date_column) BETWEEN %s AND %s",
42 $start_date,
43 $end_date
44 );
45 }
46 } else {
47 // Fall back to date_range (number of days)
48 $date_range = isset($args['date_range']) ? absint($args['date_range']) : 30;
49
50 if ($date_range > 0) {
51 $date_condition = $wpdb->prepare(
52 "AND $date_column >= DATE_SUB(NOW(), INTERVAL %d DAY)",
53 $date_range
54 );
55 }
56 }
57 return $date_condition;
58 }
59
60 /**
61 * Get pro analytics data
62 *
63 * @param array $args
64 * @return array
65 */
66 public function get_pro_analytics_data($args = [])
67 {
68 $data = [
69 'unique_viewers_per_embed' => $this->get_unique_viewers_per_embed($args),
70 'geo_analytics' => $this->get_geo_analytics($args),
71 'device_analytics' => $this->get_device_analytics($args),
72 'referral_analytics' => $this->get_referral_analytics($args),
73 'browser_analytics' => $this->get_browser_analytics($args)
74 ];
75
76 return $data;
77 }
78
79 /**
80 * Get detailed content analytics (per embed)
81 *
82 * @param array $args
83 * @return array
84 */
85 public function get_detailed_content_analytics($args = [])
86 {
87 global $wpdb;
88
89 $content_table = $wpdb->prefix . 'embedpress_analytics_content';
90 $views_table = $wpdb->prefix . 'embedpress_analytics_views';
91 $date_condition = $this->build_date_condition($args, 'created_at');
92
93 $order_by_total_views = isset($args['order_by_total_views']) ? (bool) $args['order_by_total_views'] : false;
94
95 // Handle limit parameter
96 $limit = isset($args['limit']) ? (int) $args['limit'] : 20;
97 $limit_clause = $limit > 0 ? "LIMIT $limit" : '';
98
99 // Build the date condition for subqueries
100 $subquery_date_condition = '';
101 if (!empty($date_condition)) {
102 $subquery_date_condition = str_replace('AND ', 'AND ', $date_condition);
103 }
104
105 if (!empty($date_condition)) {
106 // Date filtering applied
107 return $wpdb->get_results(
108 "SELECT
109 c.content_id,
110 c.embed_type,
111 c.title,
112 COALESCE(c.total_views, 0) as total_views,
113 COALESCE(c.total_clicks, 0) as total_clicks,
114 COALESCE(c.total_impressions, 0) as total_impressions,
115 COALESCE(view_counts.actual_views, 0) as actual_views,
116 COALESCE(click_counts.actual_clicks, 0) as actual_clicks,
117 COALESCE(impression_counts.actual_impressions, 0) as actual_impressions
118 FROM $content_table c
119 INNER JOIN (
120 SELECT DISTINCT content_id
121 FROM $views_table
122 WHERE 1=1 $subquery_date_condition
123 ) active_content ON c.content_id = active_content.content_id
124 LEFT JOIN (
125 SELECT content_id,
126 (COUNT(CASE WHEN interaction_type = 'view' THEN 1 END) +
127 COALESCE(SUM(CASE WHEN (interaction_type = 'combined' OR interaction_type = '' OR interaction_type IS NULL) THEN JSON_EXTRACT(interaction_data, '$.view_count') END), 0)) as actual_views
128 FROM $views_table
129 WHERE (interaction_type = 'view' OR interaction_type = 'combined' OR interaction_type = '' OR interaction_type IS NULL) $subquery_date_condition
130 GROUP BY content_id
131 ) view_counts ON c.content_id = view_counts.content_id
132 LEFT JOIN (
133 SELECT content_id,
134 (COUNT(CASE WHEN interaction_type = 'click' THEN 1 END) +
135 COALESCE(SUM(CASE WHEN (interaction_type = 'combined' OR interaction_type = '' OR interaction_type IS NULL) THEN JSON_EXTRACT(interaction_data, '$.click_count') END), 0)) as actual_clicks
136 FROM $views_table
137 WHERE (interaction_type = 'click' OR interaction_type = 'combined' OR interaction_type = '' OR interaction_type IS NULL) $subquery_date_condition
138 GROUP BY content_id
139 ) click_counts ON c.content_id = click_counts.content_id
140 LEFT JOIN (
141 SELECT content_id,
142 (COUNT(CASE WHEN interaction_type = 'impression' THEN 1 END) +
143 COALESCE(SUM(CASE WHEN (interaction_type = 'combined' OR interaction_type = '' OR interaction_type IS NULL) THEN JSON_EXTRACT(interaction_data, '$.impression_count') END), 0)) as actual_impressions
144 FROM $views_table
145 WHERE (interaction_type = 'impression' OR interaction_type = 'combined' OR interaction_type = '' OR interaction_type IS NULL) $subquery_date_condition
146 GROUP BY content_id
147 ) impression_counts ON c.content_id = impression_counts.content_id
148 ORDER BY " . ($order_by_total_views ? "COALESCE(c.total_views, 0)" : "COALESCE(view_counts.actual_views, 0)") . " DESC
149 $limit_clause",
150 ARRAY_A
151 );
152 } else {
153 // No date filtering
154 return $wpdb->get_results(
155 "SELECT
156 c.content_id,
157 c.embed_type,
158 c.title,
159 COALESCE(c.total_views, 0) as total_views,
160 COALESCE(c.total_clicks, 0) as total_clicks,
161 COALESCE(c.total_impressions, 0) as total_impressions,
162 COALESCE(view_counts.actual_views, 0) as actual_views,
163 COALESCE(click_counts.actual_clicks, 0) as actual_clicks,
164 COALESCE(impression_counts.actual_impressions, 0) as actual_impressions
165 FROM $content_table c
166 LEFT JOIN (
167 SELECT content_id,
168 (COUNT(CASE WHEN interaction_type = 'view' THEN 1 END) +
169 COALESCE(SUM(CASE WHEN (interaction_type = 'combined' OR interaction_type = '' OR interaction_type IS NULL) THEN JSON_EXTRACT(interaction_data, '$.view_count') END), 0)) as actual_views
170 FROM $views_table
171 WHERE interaction_type IN ('view', 'combined', '') OR interaction_type IS NULL
172 GROUP BY content_id
173 ) view_counts ON c.content_id = view_counts.content_id
174 LEFT JOIN (
175 SELECT content_id,
176 (COUNT(CASE WHEN interaction_type = 'click' THEN 1 END) +
177 COALESCE(SUM(CASE WHEN (interaction_type = 'combined' OR interaction_type = '' OR interaction_type IS NULL) THEN JSON_EXTRACT(interaction_data, '$.click_count') END), 0)) as actual_clicks
178 FROM $views_table
179 WHERE interaction_type IN ('click', 'combined', '') OR interaction_type IS NULL
180 GROUP BY content_id
181 ) click_counts ON c.content_id = click_counts.content_id
182 LEFT JOIN (
183 SELECT content_id,
184 (COUNT(CASE WHEN interaction_type = 'impression' THEN 1 END) +
185 COALESCE(SUM(CASE WHEN (interaction_type = 'combined' OR interaction_type = '' OR interaction_type IS NULL) THEN JSON_EXTRACT(interaction_data, '$.impression_count') END), 0)) as actual_impressions
186 FROM $views_table
187 WHERE interaction_type IN ('impression', 'combined', '') OR interaction_type IS NULL
188 GROUP BY content_id
189 ) impression_counts ON c.content_id = impression_counts.content_id
190 ORDER BY COALESCE(c.total_views, 0) DESC
191 $limit_clause",
192 ARRAY_A
193 );
194 }
195 }
196
197
198 /**
199 * Get unique viewers per embed
200 *
201 * @param array $args
202 * @return array
203 */
204 public function get_unique_viewers_per_embed($args = [])
205 {
206 global $wpdb;
207
208 $views_table = $wpdb->prefix . 'embedpress_analytics_views';
209 $content_table = $wpdb->prefix . 'embedpress_analytics_content';
210 $date_condition = $this->build_date_condition($args, 'v.created_at');
211
212 return $wpdb->get_results(
213 "SELECT
214 c.content_id,
215 c.title,
216 c.embed_type,
217 COUNT(DISTINCT v.session_id) as unique_viewers,
218 (COUNT(CASE WHEN v.interaction_type = 'view' THEN v.id END) +
219 COALESCE(SUM(CASE WHEN (v.interaction_type = 'combined' OR v.interaction_type = '' OR v.interaction_type IS NULL) THEN JSON_EXTRACT(v.interaction_data, '$.view_count') END), 0)) as total_views,
220 (COUNT(CASE WHEN v.interaction_type = 'click' THEN v.id END) +
221 COALESCE(SUM(CASE WHEN (v.interaction_type = 'combined' OR v.interaction_type = '' OR v.interaction_type IS NULL) THEN JSON_EXTRACT(v.interaction_data, '$.click_count') END), 0)) as total_clicks,
222 (COUNT(CASE WHEN v.interaction_type = 'impression' THEN v.id END) +
223 COALESCE(SUM(CASE WHEN (v.interaction_type = 'combined' OR v.interaction_type = '' OR v.interaction_type IS NULL) THEN JSON_EXTRACT(v.interaction_data, '$.impression_count') END), 0)) as total_impressions
224 FROM $content_table c
225 LEFT JOIN $views_table v ON c.content_id = v.content_id
226 WHERE (v.interaction_type IN ('view', 'click', 'impression', 'combined') OR v.interaction_type = '' OR v.interaction_type IS NULL)
227 $date_condition
228 GROUP BY c.content_id
229 ORDER BY unique_viewers DESC
230 LIMIT 20",
231 ARRAY_A
232 );
233 }
234
235 /**
236 * Get geo analytics
237 *
238 * @param array $args
239 * @return array
240 */
241 public function get_geo_analytics($args = [])
242 {
243 // Check if pro feature is available
244 if (!License_Manager::has_analytics_feature('geo_tracking')) {
245 return [];
246 }
247
248 global $wpdb;
249
250 $browser_table = $wpdb->prefix . 'embedpress_analytics_browser_info';
251 $views_table = $wpdb->prefix . 'embedpress_analytics_views';
252 $date_condition = $this->build_date_condition($args, 'v.created_at');
253
254 // Get country distribution with separate clicks, views, impressions
255 $countries = $wpdb->get_results(
256 "SELECT
257 COALESCE(b.country, 'Unknown') as country,
258 COUNT(DISTINCT v.session_id) as visitors,
259 (COUNT(CASE WHEN v.interaction_type = 'view' THEN v.id END) +
260 COALESCE(SUM(CASE WHEN (v.interaction_type = 'combined' OR v.interaction_type = '' OR v.interaction_type IS NULL) THEN JSON_EXTRACT(v.interaction_data, '$.view_count') END), 0)) as views,
261 (COUNT(CASE WHEN v.interaction_type = 'click' THEN v.id END) +
262 COALESCE(SUM(CASE WHEN (v.interaction_type = 'combined' OR v.interaction_type = '' OR v.interaction_type IS NULL) THEN JSON_EXTRACT(v.interaction_data, '$.click_count') END), 0)) as clicks,
263 (COUNT(CASE WHEN v.interaction_type = 'impression' THEN v.id END) +
264 COALESCE(SUM(CASE WHEN (v.interaction_type = 'combined' OR v.interaction_type = '' OR v.interaction_type IS NULL) THEN JSON_EXTRACT(v.interaction_data, '$.impression_count') END), 0)) as impressions,
265 COUNT(v.id) as total_interactions
266 FROM $browser_table b
267 INNER JOIN $views_table v ON (
268 b.user_id = CONCAT('user:', v.session_id) OR
269 b.user_id = CONCAT('guest:', v.session_id) OR
270 b.session_id = v.session_id
271 )
272 WHERE 1=1 $date_condition
273 GROUP BY b.country
274 ORDER BY visitors DESC
275 LIMIT 20",
276 ARRAY_A
277 );
278
279 // Get city distribution for top countries
280 $cities = $wpdb->get_results(
281 "SELECT
282 COALESCE(b.country, 'Unknown') as country,
283 COALESCE(b.city, 'Unknown') as city,
284 COUNT(DISTINCT v.session_id) as visitors
285 FROM $browser_table b
286 INNER JOIN $views_table v ON (
287 b.user_id = CONCAT('user:', v.session_id) OR
288 b.user_id = CONCAT('guest:', v.session_id) OR
289 b.session_id = v.session_id
290 )
291 WHERE 1=1 $date_condition
292 GROUP BY b.country, b.city
293 ORDER BY visitors DESC
294 LIMIT 50",
295 ARRAY_A
296 );
297
298
299 // Return real data only - no sample data fallback
300 if (empty($countries)) {
301 $countries = [];
302 }
303
304 if (empty($cities)) {
305 $cities = [];
306 }
307
308 return [
309 'countries' => $countries,
310 'cities' => $cities
311 ];
312 }
313
314 /**
315 * Get combined monthly analytics for spline chart (Jan-Dec)
316 *
317 * @param array $args
318 * @return array
319 */
320 public function get_daily_combined_analytics($args = [])
321 {
322 global $wpdb;
323
324 $views_table = $wpdb->prefix . 'embedpress_analytics_views';
325 $date_condition = $this->build_date_condition($args, 'created_at');
326
327 // If date filtering is applied, return empty data for past dates
328 if (!empty($date_condition)) {
329 // Check if the date range has any data
330 $has_data = $wpdb->get_var(
331 "SELECT COUNT(*) FROM $views_table WHERE 1=1 $date_condition"
332 );
333
334 if (!$has_data) {
335 // Return empty monthly data for all months
336 $chart_data = [];
337 $months = [
338 1 => 'JAN',
339 2 => 'FEB',
340 3 => 'MAR',
341 4 => 'APR',
342 5 => 'MAY',
343 6 => 'JUN',
344 7 => 'JUL',
345 8 => 'AUG',
346 9 => 'SEP',
347 10 => 'OCT',
348 11 => 'NOV',
349 12 => 'DEC'
350 ];
351
352 for ($month = 1; $month <= 12; $month++) {
353 $chart_data[] = [
354 'month' => $months[$month],
355 'views' => 0,
356 'clicks' => 0,
357 'impressions' => 0
358 ];
359 }
360
361 return $chart_data;
362 }
363 }
364
365 $current_year = date('Y');
366
367 // Get monthly data for current year
368 $monthly_data = $wpdb->get_results($wpdb->prepare(
369 "SELECT
370 MONTH(created_at) as month_num,
371 MONTHNAME(created_at) as month_name,
372 (SUM(CASE WHEN interaction_type = 'view' THEN 1 ELSE 0 END) +
373 COALESCE(SUM(CASE WHEN (interaction_type = 'combined' OR interaction_type = '' OR interaction_type IS NULL) THEN JSON_EXTRACT(interaction_data, '$.view_count') END), 0)) as views,
374 (SUM(CASE WHEN interaction_type = 'click' THEN 1 ELSE 0 END) +
375 COALESCE(SUM(CASE WHEN (interaction_type = 'combined' OR interaction_type = '' OR interaction_type IS NULL) THEN JSON_EXTRACT(interaction_data, '$.click_count') END), 0)) as clicks,
376 (SUM(CASE WHEN interaction_type = 'impression' THEN 1 ELSE 0 END) +
377 COALESCE(SUM(CASE WHEN (interaction_type = 'combined' OR interaction_type = '' OR interaction_type IS NULL) THEN JSON_EXTRACT(interaction_data, '$.impression_count') END), 0)) as impressions
378 FROM $views_table
379 WHERE YEAR(created_at) = %d
380 GROUP BY MONTH(created_at), MONTHNAME(created_at)
381 ORDER BY MONTH(created_at) ASC",
382 $current_year
383 ), ARRAY_A);
384
385 // Create array for all 12 months
386 $months = [
387 1 => 'JAN',
388 2 => 'FEB',
389 3 => 'MAR',
390 4 => 'APR',
391 5 => 'MAY',
392 6 => 'JUN',
393 7 => 'JUL',
394 8 => 'AUG',
395 9 => 'SEP',
396 10 => 'OCT',
397 11 => 'NOV',
398 12 => 'DEC'
399 ];
400
401 $chart_data = [];
402
403 for ($month = 1; $month <= 12; $month++) {
404 // Find data for this month
405 $month_data = null;
406 foreach ($monthly_data as $data) {
407 if ((int) $data['month_num'] === $month) {
408 $month_data = $data;
409 break;
410 }
411 }
412
413 // Add some realistic variation to the data if no real data exists
414 $base_views = $month_data ? (int) $month_data['views'] : 0;
415 $base_clicks = $month_data ? (int) $month_data['clicks'] : 0;
416 $base_impressions = $month_data ? (int) $month_data['impressions'] : 0;
417
418 $chart_data[] = [
419 'month' => $months[$month],
420 'views' => (int) $base_views,
421 'clicks' => (int) $base_clicks,
422 'impressions' => (int) $base_impressions
423 ];
424 }
425
426 return $chart_data;
427 }
428
429 /**
430 * Get seasonal factor for realistic data variation
431 *
432 * @param int $month
433 * @return float
434 */
435 private function get_seasonal_factor($month)
436 {
437 // Simulate seasonal trends (higher activity in certain months)
438 $factors = [
439 1 => 0.8, // Jan - lower after holidays
440 2 => 0.9, // Feb
441 3 => 1.1, // Mar - spring increase
442 4 => 1.0, // Apr
443 5 => 0.9, // May
444 6 => 0.8, // Jun - summer dip
445 7 => 1.2, // Jul - summer peak
446 8 => 1.0, // Aug
447 9 => 1.3, // Sep - back to school/work
448 10 => 1.4, // Oct - peak activity
449 11 => 1.5, // Nov - holiday season
450 12 => 1.2 // Dec - holiday season
451 ];
452
453 return $factors[$month] ?? 1.0;
454 }
455
456 /**
457 * Get device analytics
458 *
459 * @param array $args
460 * @return array
461 */
462 public function get_device_analytics($args = [])
463 {
464 // Check if pro feature is available
465 if (!License_Manager::has_analytics_feature('device_analytics')) {
466 return [];
467 }
468
469 global $wpdb;
470
471 $browser_table = $wpdb->prefix . 'embedpress_analytics_browser_info';
472 $views_table = $wpdb->prefix . 'embedpress_analytics_views';
473 $date_condition = $this->build_date_condition($args, 'v.created_at');
474
475 // Device type distribution
476 $devices = $wpdb->get_results(
477 "SELECT
478 b.device_type,
479 COUNT(DISTINCT v.session_id) as visitors,
480 COUNT(v.id) as total_interactions
481 FROM $browser_table b
482 INNER JOIN $views_table v ON (
483 b.user_id = CONCAT('user:', v.session_id) OR
484 b.user_id = CONCAT('guest:', v.session_id) OR
485 b.session_id = v.session_id
486 )
487 WHERE 1=1 $date_condition
488 GROUP BY b.device_type
489 ORDER BY visitors DESC",
490 ARRAY_A
491 );
492
493 // Screen resolution distribution
494 $resolutions = $wpdb->get_results(
495 "SELECT
496 b.screen_resolution,
497 COUNT(DISTINCT v.session_id) as visitors
498 FROM $browser_table b
499 INNER JOIN $views_table v ON b.session_id = v.session_id
500 WHERE b.screen_resolution IS NOT NULL AND b.screen_resolution != ''
501 $date_condition
502 GROUP BY b.screen_resolution
503 ORDER BY visitors DESC
504 LIMIT 10",
505 ARRAY_A
506 );
507
508 // Return real data only - no sample data fallback
509 if (empty($devices)) {
510 $devices = [];
511 }
512
513 if (empty($resolutions)) {
514 $resolutions = [];
515 }
516
517 return [
518 'devices' => $devices,
519 'resolutions' => $resolutions
520 ];
521 }
522
523 /**
524 * Get referral analytics
525 *
526 * @param array $args
527 * @return array
528 */
529 public function get_referral_analytics($args = [])
530 {
531 global $wpdb;
532
533 $views_table = $wpdb->prefix . 'embedpress_analytics_views';
534 $date_condition = $this->build_date_condition($args, 'created_at');
535
536 $results = $wpdb->get_results(
537 "SELECT
538 CASE
539 WHEN referrer_url IS NULL OR referrer_url = '' THEN 'Direct'
540 WHEN referrer_url LIKE '%google.%' THEN 'Google'
541 WHEN referrer_url LIKE '%facebook.%' OR referrer_url LIKE '%l.facebook.com%' OR referrer_url LIKE '%fbclid%' THEN 'Facebook'
542 WHEN referrer_url LIKE '%twitter.%' OR referrer_url LIKE '%t.co%' THEN 'Twitter'
543 WHEN referrer_url LIKE '%linkedin.%' THEN 'LinkedIn'
544 WHEN referrer_url LIKE '%youtube.%' THEN 'YouTube'
545 WHEN referrer_url LIKE '%instagram.%' THEN 'Instagram'
546 WHEN referrer_url LIKE '%tiktok.%' THEN 'TikTok'
547 ELSE SUBSTRING_INDEX(SUBSTRING_INDEX(referrer_url, '/', 3), '/', -1)
548 END as referrer_source,
549 COUNT(DISTINCT session_id) as visitors,
550 COUNT(*) as total_visits,
551 referrer_url
552 FROM $views_table
553 WHERE (interaction_type IN ('view', 'impression', 'combined') OR interaction_type = '' OR interaction_type IS NULL)
554 $date_condition
555 GROUP BY referrer_source, referrer_url
556 ORDER BY visitors DESC
557 LIMIT 100",
558 ARRAY_A
559 );
560
561 // Process results to extract UTM parameters and calculate percentages
562 $total_visits_sum = 0;
563
564 // First pass: calculate total visits for percentage calculation
565 foreach ($results as $result) {
566 $total_visits_sum += intval($result['total_visits']);
567 }
568
569 // Group by source and extract UTM parameters
570 $grouped_sources = [];
571 foreach ($results as $result) {
572 $source = $result['referrer_source'];
573 $utm_source = $this->extract_utm_parameter($result['referrer_url'], 'utm_source');
574 $utm_medium = $this->extract_utm_parameter($result['referrer_url'], 'utm_medium');
575 $utm_campaign = $this->extract_utm_parameter($result['referrer_url'], 'utm_campaign');
576
577 // Use UTM source if available, otherwise use referrer source
578 if (!empty($utm_source)) {
579 $source = $utm_source;
580 if (!empty($utm_medium)) {
581 $source .= ' (' . $utm_medium . ')';
582 }
583 if (!empty($utm_campaign)) {
584 $source .= ' - ' . $utm_campaign;
585 }
586 }
587
588 if (!isset($grouped_sources[$source])) {
589 $grouped_sources[$source] = [
590 'source' => $source,
591 'visitors' => 0,
592 'total_visits' => 0,
593 'utm_data' => [
594 'utm_source' => $utm_source,
595 'utm_medium' => $utm_medium,
596 'utm_campaign' => $utm_campaign
597 ]
598 ];
599 }
600
601 $grouped_sources[$source]['visitors'] += intval($result['visitors']);
602 $grouped_sources[$source]['total_visits'] += intval($result['total_visits']);
603 }
604
605 // Calculate percentages and format final results
606 foreach ($grouped_sources as &$source_data) {
607 $percentage = $total_visits_sum > 0 ? round(($source_data['total_visits'] / $total_visits_sum) * 100, 1) : 0;
608 $source_data['percentage'] = $percentage;
609 }
610
611 // Sort by visitors and limit to top 20
612 uasort($grouped_sources, function($a, $b) {
613 return $b['visitors'] - $a['visitors'];
614 });
615
616 $final_results = array_slice(array_values($grouped_sources), 0, 20);
617
618 // Return structured data for frontend
619 return [
620 'referral_sources' => $final_results,
621 'total_sources' => count($final_results),
622 'total_visits' => $total_visits_sum
623 ];
624 }
625
626 /**
627 * Extract UTM parameter from URL
628 *
629 * @param string $url
630 * @param string $parameter
631 * @return string
632 */
633 private function extract_utm_parameter($url, $parameter)
634 {
635 if (empty($url)) {
636 return '';
637 }
638
639 $parsed_url = parse_url($url);
640 if (!isset($parsed_url['query'])) {
641 return '';
642 }
643
644 parse_str($parsed_url['query'], $query_params);
645 return isset($query_params[$parameter]) ? sanitize_text_field($query_params[$parameter]) : '';
646 }
647
648 /**
649 * Get browser analytics
650 *
651 * @param array $args
652 * @return array
653 */
654 public function get_browser_analytics($args = [])
655 {
656 global $wpdb;
657
658 $table_name = $wpdb->prefix . 'embedpress_analytics_browser_info';
659 $date_condition = $this->build_date_condition($args, 'created_at');
660
661 // Browser distribution
662 $browsers = $wpdb->get_results(
663 "SELECT browser_name, COUNT(*) as count
664 FROM $table_name
665 WHERE browser_name IS NOT NULL
666 $date_condition
667 GROUP BY browser_name
668 ORDER BY count DESC",
669 ARRAY_A
670 );
671
672 // Operating system distribution
673 $os = $wpdb->get_results(
674 "SELECT operating_system, COUNT(*) as count
675 FROM $table_name
676 WHERE operating_system IS NOT NULL
677 $date_condition
678 GROUP BY operating_system
679 ORDER BY count DESC",
680 ARRAY_A
681 );
682
683 // Device type distribution
684 $devices = $wpdb->get_results(
685 "SELECT device_type, COUNT(*) as count
686 FROM $table_name
687 WHERE device_type IS NOT NULL
688 $date_condition
689 GROUP BY device_type
690 ORDER BY count DESC",
691 ARRAY_A
692 );
693
694 return [
695 'browsers' => $browsers,
696 'operating_systems' => $os,
697 'devices' => $devices
698 ];
699 }
700 }
701