PluginProbe ʕ •ᴥ•ʔ
Download Manager / 3.3.63
Download Manager v3.3.63
3.3.63 3.3.62 3.3.61 3.3.60 3.3.59 3.3.58 3.3.57 3.3.56 trunk 2.1.3 2.3.0 2.5.96 2.5.97 2.6.2 2.6.96 2.8.3 2.9.99 3.0.4 3.1.05 3.1.07 3.1.08 3.1.11 3.1.12 3.1.14 3.1.17 3.1.18 3.1.22 3.1.23 3.1.24 3.1.25 3.1.26 3.1.27 3.1.28 3.2.04 3.2.13 3.2.14 3.2.16 3.2.18 3.2.19 3.2.21 3.2.22 3.2.23 3.2.24 3.2.25 3.2.27 3.2.28 3.2.29 3.2.30 3.2.31 3.2.32 3.2.33 3.2.34 3.2.35 3.2.37 3.2.38 3.2.39 3.2.40 3.2.41 3.2.42 3.2.43 3.2.44 3.2.45 3.2.46 3.2.47 3.2.48 3.2.49 3.2.50 3.2.51 3.2.52 3.2.53 3.2.54 3.2.55 3.2.56 3.2.57 3.2.58 3.2.59 3.2.60 3.2.61 3.2.63 3.2.64 3.2.65 3.2.66 3.2.67 3.2.68 3.2.69 3.2.70 3.2.71 3.2.72 3.2.73 3.2.74 3.2.75 3.2.76 3.2.77 3.2.78 3.2.79 3.2.80 3.2.81 3.2.82 3.2.83 3.2.84 3.2.85 3.2.86 3.2.87 3.2.88 3.2.89 3.2.90 3.2.91 3.2.92 3.2.93 3.2.94 3.2.95 3.2.96 3.2.97 3.2.98 3.2.99 3.3.00 3.3.01 3.3.02 3.3.03 3.3.04 3.3.05 3.3.06 3.3.07 3.3.08 3.3.09 3.3.10 3.3.11 3.3.12 3.3.13 3.3.14 3.3.15 3.3.16 3.3.17 3.3.18 3.3.19 3.3.20 3.3.21 3.3.22 3.3.23 3.3.24 3.3.25 3.3.26 3.3.27 3.3.28 3.3.29 3.3.30 3.3.31 3.3.32 3.3.33 3.3.34 3.3.35 3.3.36 3.3.37 3.3.38 3.3.39 3.3.40 3.3.41 3.3.42 3.3.43 3.3.44 3.3.45 3.3.46 3.3.47 3.3.48 3.3.49 3.3.50 3.3.51 3.3.52 3.3.53 3.3.54 3.3.55
download-manager / src / __ / ActivityReport.php
download-manager / src / __ Last commit date
HTML 1 year ago Jobs 1 day ago views 1 day ago ActivityReport.php 1 day ago Apply.php 6 months ago Cron.php 1 year ago CronJob.php 1 day ago CronJobs.php 1 day ago Crypt.php 2 months ago DownloadStats.php 5 months ago Email.php 1 day ago EmailCron.php 1 year ago FileSystem.php 1 year ago Installer.php 1 day ago Messages.php 1 year ago Query.php 5 months ago Session.php 1 week ago Settings.php 5 years ago SimpleMath.php 5 years ago TempStorage.php 1 week ago Template.php 5 months ago UI.php 7 months ago Updater.php 4 years ago UserAgent.php 2 years ago __.php 2 months ago __MailUI.php 3 years ago
ActivityReport.php
766 lines
1 <?php
2 /**
3 * Activity Report Generator
4 *
5 * Collects download statistics and generates activity reports.
6 *
7 * @package WPDM\__
8 * @since 7.0.2
9 */
10
11 namespace WPDM\__;
12
13 class ActivityReport
14 {
15 /**
16 * Start date of the current reporting period (timestamp)
17 *
18 * @var int
19 */
20 private $startDate;
21
22 /**
23 * End date of the current reporting period (timestamp)
24 *
25 * @var int
26 */
27 private $endDate;
28
29 /**
30 * Start date of the previous period for comparison (timestamp)
31 *
32 * @var int
33 */
34 private $previousStartDate;
35
36 /**
37 * End date of the previous period for comparison (timestamp)
38 *
39 * @var int
40 */
41 private $previousEndDate;
42
43 /**
44 * Report frequency
45 *
46 * @var string weekly|monthly
47 */
48 private $frequency;
49
50 /**
51 * Constructor
52 *
53 * @param string $frequency 'weekly' or 'monthly'
54 */
55 public function __construct(string $frequency = 'weekly')
56 {
57 $this->frequency = $frequency;
58 $this->calculateDateRanges($frequency);
59 }
60
61 /**
62 * Calculate date ranges based on frequency
63 *
64 * @param string $frequency
65 * @return void
66 */
67 private function calculateDateRanges(string $frequency): void
68 {
69 if ($frequency === 'monthly') {
70 // Previous calendar month
71 $this->startDate = strtotime('first day of last month midnight');
72 $this->endDate = strtotime('last day of last month 23:59:59');
73
74 // Month before that for comparison
75 $this->previousStartDate = strtotime('first day of -2 months midnight');
76 $this->previousEndDate = strtotime('last day of -2 months 23:59:59');
77 } else {
78 // Weekly: Last 7 complete days (Monday to Sunday of previous week)
79 $this->startDate = strtotime('monday last week midnight');
80 $this->endDate = strtotime('sunday last week 23:59:59');
81
82 // Week before that for comparison
83 $this->previousStartDate = strtotime('monday -2 weeks midnight');
84 $this->previousEndDate = strtotime('sunday -2 weeks 23:59:59');
85 }
86 }
87
88 /**
89 * Get download summary statistics
90 *
91 * @return array
92 */
93 public function getDownloadSummary(): array
94 {
95 global $wpdb;
96
97 // Current period downloads
98 $currentDownloads = (int) $wpdb->get_var($wpdb->prepare(
99 "SELECT COUNT(*) FROM {$wpdb->prefix}ahm_download_stats
100 WHERE timestamp >= %d AND timestamp <= %d",
101 $this->startDate,
102 $this->endDate
103 ));
104
105 // Previous period downloads
106 $previousDownloads = (int) $wpdb->get_var($wpdb->prepare(
107 "SELECT COUNT(*) FROM {$wpdb->prefix}ahm_download_stats
108 WHERE timestamp >= %d AND timestamp <= %d",
109 $this->previousStartDate,
110 $this->previousEndDate
111 ));
112
113 // Daily average
114 $days = max(1, ($this->endDate - $this->startDate) / 86400);
115 $dailyAverage = round($currentDownloads / $days, 1);
116
117 // Peak day
118 $peakDay = $wpdb->get_row($wpdb->prepare(
119 "SELECT DATE(FROM_UNIXTIME(timestamp)) as date, COUNT(*) as count
120 FROM {$wpdb->prefix}ahm_download_stats
121 WHERE timestamp >= %d AND timestamp <= %d
122 GROUP BY DATE(FROM_UNIXTIME(timestamp))
123 ORDER BY count DESC
124 LIMIT 1",
125 $this->startDate,
126 $this->endDate
127 ));
128
129 return [
130 'total' => $currentDownloads,
131 'previous' => $previousDownloads,
132 'change' => $this->calculateChange($currentDownloads, $previousDownloads),
133 'change_class' => $currentDownloads >= $previousDownloads ? 'positive' : 'negative',
134 'daily_average' => $dailyAverage,
135 'peak_day' => $peakDay ? date_i18n(get_option('date_format'), strtotime($peakDay->date)) : '-',
136 'peak_day_count' => $peakDay ? (int) $peakDay->count : 0,
137 ];
138 }
139
140 /**
141 * Get top downloaded packages
142 *
143 * @param int $limit
144 * @return array
145 */
146 public function getTopPackages(int $limit = 5): array
147 {
148 global $wpdb;
149
150 $results = $wpdb->get_results($wpdb->prepare(
151 "SELECT pid, COUNT(*) as downloads
152 FROM {$wpdb->prefix}ahm_download_stats
153 WHERE timestamp >= %d AND timestamp <= %d
154 GROUP BY pid
155 ORDER BY downloads DESC
156 LIMIT %d",
157 $this->startDate,
158 $this->endDate,
159 $limit
160 ));
161
162 $packages = [];
163 $maxDownloads = 0;
164
165 foreach ($results as $row) {
166 if ((int) $row->downloads > $maxDownloads) {
167 $maxDownloads = (int) $row->downloads;
168 }
169 }
170
171 foreach ($results as $index => $row) {
172 $post = get_post($row->pid);
173 if (!$post) continue;
174
175 $totalDownloads = (int) get_post_meta($row->pid, '__wpdm_download_count', true);
176 $barWidth = $maxDownloads > 0 ? round(($row->downloads / $maxDownloads) * 100) : 0;
177
178 $packages[] = [
179 'rank' => $index + 1,
180 'id' => $row->pid,
181 'title' => $post->post_title,
182 'url' => get_permalink($row->pid),
183 'edit_url' => admin_url('post.php?post=' . $row->pid . '&action=edit'),
184 'downloads' => (int) $row->downloads,
185 'total_downloads' => $totalDownloads,
186 'bar_width' => $barWidth,
187 ];
188 }
189
190 return $packages;
191 }
192
193 /**
194 * Get trending packages (biggest growth)
195 *
196 * @param int $limit
197 * @return array
198 */
199 public function getTrendingPackages(int $limit = 5): array
200 {
201 global $wpdb;
202
203 // Get downloads for current period by package
204 $currentData = $wpdb->get_results($wpdb->prepare(
205 "SELECT pid, COUNT(*) as downloads
206 FROM {$wpdb->prefix}ahm_download_stats
207 WHERE timestamp >= %d AND timestamp <= %d
208 GROUP BY pid",
209 $this->startDate,
210 $this->endDate
211 ), OBJECT_K);
212
213 // Get downloads for previous period by package
214 $previousData = $wpdb->get_results($wpdb->prepare(
215 "SELECT pid, COUNT(*) as downloads
216 FROM {$wpdb->prefix}ahm_download_stats
217 WHERE timestamp >= %d AND timestamp <= %d
218 GROUP BY pid",
219 $this->previousStartDate,
220 $this->previousEndDate
221 ), OBJECT_K);
222
223 $trending = [];
224
225 foreach ($currentData as $pid => $current) {
226 $currentCount = (int) $current->downloads;
227 $previousCount = isset($previousData[$pid]) ? (int) $previousData[$pid]->downloads : 0;
228
229 // Calculate growth percentage
230 if ($previousCount > 0) {
231 $growth = (($currentCount - $previousCount) / $previousCount) * 100;
232 } else if ($currentCount > 0) {
233 $growth = 100; // New downloads (was 0, now has some)
234 } else {
235 continue;
236 }
237
238 // Only include packages with positive growth
239 if ($growth > 0) {
240 $trending[$pid] = [
241 'pid' => $pid,
242 'current' => $currentCount,
243 'previous' => $previousCount,
244 'growth' => $growth,
245 ];
246 }
247 }
248
249 // Sort by growth percentage
250 uasort($trending, function ($a, $b) {
251 return $b['growth'] <=> $a['growth'];
252 });
253
254 // Limit and enrich with post data
255 $trending = array_slice($trending, 0, $limit, true);
256 $packages = [];
257
258 foreach ($trending as $item) {
259 $post = get_post($item['pid']);
260 if (!$post) continue;
261
262 $packages[] = [
263 'id' => $item['pid'],
264 'title' => $post->post_title,
265 'url' => get_permalink($item['pid']),
266 'current_downloads' => $item['current'],
267 'previous_downloads' => $item['previous'],
268 'growth' => round($item['growth'], 1),
269 'growth_text' => '+' . round($item['growth'], 1) . '%',
270 ];
271 }
272
273 return $packages;
274 }
275
276 /**
277 * Get user activity statistics
278 *
279 * @return array
280 */
281 public function getUserActivity(): array
282 {
283 global $wpdb;
284
285 // New users registered this period
286 $newUsers = (int) $wpdb->get_var($wpdb->prepare(
287 "SELECT COUNT(*) FROM {$wpdb->users}
288 WHERE user_registered >= %s AND user_registered <= %s",
289 date('Y-m-d H:i:s', $this->startDate),
290 date('Y-m-d H:i:s', $this->endDate)
291 ));
292
293 // Unique downloaders (registered users)
294 $registeredDownloaders = (int) $wpdb->get_var($wpdb->prepare(
295 "SELECT COUNT(DISTINCT uid) FROM {$wpdb->prefix}ahm_download_stats
296 WHERE timestamp >= %d AND timestamp <= %d AND uid > 0",
297 $this->startDate,
298 $this->endDate
299 ));
300
301 // Total unique downloaders (by IP or user)
302 $totalDownloaders = (int) $wpdb->get_var($wpdb->prepare(
303 "SELECT COUNT(DISTINCT COALESCE(NULLIF(uid, 0), ip))
304 FROM {$wpdb->prefix}ahm_download_stats
305 WHERE timestamp >= %d AND timestamp <= %d",
306 $this->startDate,
307 $this->endDate
308 ));
309
310 $guestDownloaders = $totalDownloaders - $registeredDownloaders;
311
312 // Top downloaders
313 $topDownloaders = $wpdb->get_results($wpdb->prepare(
314 "SELECT uid, COUNT(*) as downloads
315 FROM {$wpdb->prefix}ahm_download_stats
316 WHERE timestamp >= %d AND timestamp <= %d AND uid > 0
317 GROUP BY uid
318 ORDER BY downloads DESC
319 LIMIT 5",
320 $this->startDate,
321 $this->endDate
322 ));
323
324 $topDownloadersList = [];
325 foreach ($topDownloaders as $row) {
326 $user = get_user_by('ID', $row->uid);
327 if ($user) {
328 $topDownloadersList[] = [
329 'id' => $row->uid,
330 'name' => $user->display_name,
331 'email' => $user->user_email,
332 'downloads' => (int) $row->downloads,
333 ];
334 }
335 }
336
337 return [
338 'new_users' => $newUsers,
339 'unique_downloaders' => $totalDownloaders,
340 'registered_downloaders' => $registeredDownloaders,
341 'guest_downloaders' => $guestDownloaders,
342 'registered_ratio' => $totalDownloaders > 0
343 ? round(($registeredDownloaders / $totalDownloaders) * 100, 1)
344 : 0,
345 'top_downloaders' => $topDownloadersList,
346 ];
347 }
348
349 /**
350 * Get category breakdown
351 *
352 * @return array
353 */
354 public function getCategoryBreakdown(): array
355 {
356 global $wpdb;
357
358 // Get downloads per category
359 $results = $wpdb->get_results($wpdb->prepare(
360 "SELECT t.term_id, t.name, COUNT(ds.ID) as downloads
361 FROM {$wpdb->prefix}ahm_download_stats ds
362 INNER JOIN {$wpdb->term_relationships} tr ON ds.pid = tr.object_id
363 INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
364 INNER JOIN {$wpdb->terms} t ON tt.term_id = t.term_id
365 WHERE ds.timestamp >= %d AND ds.timestamp <= %d
366 AND tt.taxonomy = 'wpdmcategory'
367 GROUP BY t.term_id
368 ORDER BY downloads DESC",
369 $this->startDate,
370 $this->endDate
371 ));
372
373 // Get previous period for comparison
374 $previousResults = $wpdb->get_results($wpdb->prepare(
375 "SELECT t.term_id, COUNT(ds.ID) as downloads
376 FROM {$wpdb->prefix}ahm_download_stats ds
377 INNER JOIN {$wpdb->term_relationships} tr ON ds.pid = tr.object_id
378 INNER JOIN {$wpdb->term_taxonomy} tt ON tr.term_taxonomy_id = tt.term_taxonomy_id
379 INNER JOIN {$wpdb->terms} t ON tt.term_id = t.term_id
380 WHERE ds.timestamp >= %d AND ds.timestamp <= %d
381 AND tt.taxonomy = 'wpdmcategory'
382 GROUP BY t.term_id",
383 $this->previousStartDate,
384 $this->previousEndDate
385 ), OBJECT_K);
386
387 $categories = [];
388 $totalDownloads = 0;
389
390 foreach ($results as $row) {
391 $totalDownloads += (int) $row->downloads;
392 }
393
394 foreach ($results as $row) {
395 $previousCount = isset($previousResults[$row->term_id])
396 ? (int) $previousResults[$row->term_id]->downloads
397 : 0;
398
399 $categories[] = [
400 'id' => $row->term_id,
401 'name' => $row->name,
402 'url' => get_term_link((int) $row->term_id, 'wpdmcategory'),
403 'downloads' => (int) $row->downloads,
404 'percentage' => $totalDownloads > 0
405 ? round(($row->downloads / $totalDownloads) * 100, 1)
406 : 0,
407 'change' => $this->calculateChange((int) $row->downloads, $previousCount),
408 ];
409 }
410
411 return $categories;
412 }
413
414 /**
415 * Get revenue summary (if Premium Packages is active)
416 *
417 * @return array|null
418 */
419 public function getRevenueSummary(): ?array
420 {
421 global $wpdb;
422
423 // Check if Premium Packages is active
424 if (!class_exists('\\WPDMPremiumPackage') && !function_exists('wpdmpp_effective_price')) {
425 return null;
426 }
427
428 // Check if orders table exists
429 $table = $wpdb->prefix . 'ahm_orders';
430 if ($wpdb->get_var("SHOW TABLES LIKE '{$table}'") !== $table) {
431 return null;
432 }
433
434 // Current period revenue
435 $currentRevenue = $wpdb->get_row($wpdb->prepare(
436 "SELECT SUM(total) as revenue, COUNT(*) as orders
437 FROM {$wpdb->prefix}ahm_orders
438 WHERE date >= %d AND date <= %d
439 AND payment_status = 'Completed'",
440 $this->startDate,
441 $this->endDate
442 ));
443
444 // Previous period revenue
445 $previousRevenue = $wpdb->get_var($wpdb->prepare(
446 "SELECT SUM(total) FROM {$wpdb->prefix}ahm_orders
447 WHERE date >= %d AND date <= %d
448 AND payment_status = 'Completed'",
449 $this->previousStartDate,
450 $this->previousEndDate
451 ));
452
453 $revenue = $currentRevenue ? (float) $currentRevenue->revenue : 0;
454 $orders = $currentRevenue ? (int) $currentRevenue->orders : 0;
455 $previousRev = (float) $previousRevenue;
456
457 // Top selling products
458 $topProducts = $wpdb->get_results($wpdb->prepare(
459 "SELECT oi.pid, SUM(oi.price * oi.quantity) as revenue, SUM(oi.quantity) as quantity
460 FROM {$wpdb->prefix}ahm_orders o
461 INNER JOIN {$wpdb->prefix}ahm_order_items oi ON oi.oid = o.order_id
462 WHERE o.date >= %d AND o.date <= %d
463 AND o.payment_status = 'Completed'
464 GROUP BY oi.pid
465 ORDER BY revenue DESC
466 LIMIT 5",
467 $this->startDate,
468 $this->endDate
469 ));
470
471 $topProductsList = [];
472 foreach ($topProducts as $row) {
473 $post = get_post($row->pid);
474 if ($post) {
475 $topProductsList[] = [
476 'id' => $row->pid,
477 'title' => $post->post_title,
478 'revenue' => (float) $row->revenue,
479 'quantity' => (int) $row->quantity,
480 ];
481 }
482 }
483
484 // Get currency
485 $currency = function_exists('wpdmpp_currency_sign') ? wpdmpp_currency_sign() : '$';
486
487 return [
488 'revenue' => $revenue,
489 'revenue_formatted' => $currency . number_format($revenue, 2),
490 'previous_revenue' => $previousRev,
491 'change' => $this->calculateChange($revenue, $previousRev),
492 'change_class' => $revenue >= $previousRev ? 'positive' : 'negative',
493 'orders' => $orders,
494 'average_order' => $orders > 0 ? $currency . number_format($revenue / $orders, 2) : $currency . '0.00',
495 'top_products' => $topProductsList,
496 'currency' => $currency,
497 ];
498 }
499
500 /**
501 * Get storage usage statistics
502 *
503 * @return array
504 */
505 public function getStorageUsage(): array
506 {
507 global $wpdb;
508
509 // Get all packages
510 $packages = get_posts([
511 'post_type' => 'wpdmpro',
512 'posts_per_page' => -1,
513 'post_status' => 'publish',
514 'fields' => 'ids',
515 ]);
516
517 $totalSize = 0;
518 $fileCount = 0;
519 $largestPackages = [];
520
521 foreach ($packages as $pid) {
522 $files = get_post_meta($pid, '__wpdm_files', true);
523 if (!is_array($files)) continue;
524
525 $packageSize = 0;
526 foreach ($files as $file) {
527 $absPath = WPDM()->fileSystem->absPath($file);
528 if (file_exists($absPath)) {
529 $size = filesize($absPath);
530 $packageSize += $size;
531 $fileCount++;
532 }
533 }
534
535 if ($packageSize > 0) {
536 $largestPackages[$pid] = $packageSize;
537 $totalSize += $packageSize;
538 }
539 }
540
541 // Sort and get top 5 largest
542 arsort($largestPackages);
543 $largestPackages = array_slice($largestPackages, 0, 5, true);
544
545 $largest = [];
546 foreach ($largestPackages as $pid => $size) {
547 $post = get_post($pid);
548 if ($post) {
549 $largest[] = [
550 'id' => $pid,
551 'title' => $post->post_title,
552 'size' => $this->formatBytes($size),
553 'size_bytes' => $size,
554 ];
555 }
556 }
557
558 // New packages this period
559 $newPackages = (int) $wpdb->get_var($wpdb->prepare(
560 "SELECT COUNT(*) FROM {$wpdb->posts}
561 WHERE post_type = 'wpdmpro'
562 AND post_status = 'publish'
563 AND post_date >= %s AND post_date <= %s",
564 date('Y-m-d H:i:s', $this->startDate),
565 date('Y-m-d H:i:s', $this->endDate)
566 ));
567
568 return [
569 'total_size' => $this->formatBytes($totalSize),
570 'total_size_bytes' => $totalSize,
571 'file_count' => $fileCount,
572 'package_count' => count($packages),
573 'new_packages' => $newPackages,
574 'largest_packages' => $largest,
575 ];
576 }
577
578 /**
579 * Generate the complete report HTML
580 *
581 * @param array $sections List of sections to include
582 * @return string
583 */
584 public function generateReport(array $sections): string
585 {
586 $data = $this->collectData($sections);
587 return $this->renderEmailContent($data, $sections);
588 }
589
590 /**
591 * Collect all report data
592 *
593 * @param array $sections
594 * @return array
595 */
596 public function collectData(array $sections): array
597 {
598 $data = [
599 'frequency' => $this->frequency,
600 'date_range' => $this->getDateRangeText(),
601 'period_label' => $this->frequency === 'monthly' ? __('Monthly', 'download-manager') : __('Weekly', 'download-manager'),
602 ];
603
604 if (in_array('download_summary', $sections)) {
605 $data['download_summary'] = $this->getDownloadSummary();
606 }
607
608 if (in_array('top_packages', $sections)) {
609 $data['top_packages'] = $this->getTopPackages();
610 }
611
612 if (in_array('trending_packages', $sections)) {
613 $data['trending_packages'] = $this->getTrendingPackages();
614 }
615
616 if (in_array('user_activity', $sections)) {
617 $data['user_activity'] = $this->getUserActivity();
618 }
619
620 if (in_array('category_breakdown', $sections)) {
621 $data['category_breakdown'] = $this->getCategoryBreakdown();
622 }
623
624 if (in_array('revenue_summary', $sections)) {
625 $revenueSummary = $this->getRevenueSummary();
626 if ($revenueSummary !== null) {
627 $data['revenue_summary'] = $revenueSummary;
628 }
629 }
630
631 if (in_array('storage_usage', $sections)) {
632 $data['storage_usage'] = $this->getStorageUsage();
633 }
634
635 return $data;
636 }
637
638 /**
639 * Render email content HTML
640 *
641 * @param array $data
642 * @param array $sections
643 * @return string
644 */
645 private function renderEmailContent(array $data, array $sections): string
646 {
647 ob_start();
648 include __DIR__ . '/views/email-templates/activity-report-content.php';
649 return ob_get_clean();
650 }
651
652 /**
653 * Send the activity report email
654 *
655 * @param array $recipients
656 * @param array $sections
657 * @return bool
658 */
659 public function sendReport(array $recipients, array $sections = []): bool
660 {
661 if (empty($recipients)) {
662 return false;
663 }
664
665 if (empty($sections)) {
666 $sections = get_option('__wpdm_activity_report_sections', [
667 'download_summary',
668 'top_packages',
669 'user_activity',
670 ]);
671 }
672
673 $data = $this->collectData($sections);
674 $reportContent = $this->renderEmailContent($data, $sections);
675
676 $params = [
677 'report_period' => $data['period_label'],
678 'date_range' => $data['date_range'],
679 'report_content' => $reportContent,
680 'settings_url' => admin_url('edit.php?post_type=wpdmpro&page=settings&tab=activity-reports'),
681 ];
682
683 $success = true;
684 foreach ($recipients as $email) {
685 $params['to_email'] = $email;
686 $params['recipient_email'] = $email;
687
688 $result = Email::send('activity-report', $params);
689 if (!$result) {
690 $success = false;
691 }
692 }
693
694 return $success;
695 }
696
697 /**
698 * Get human-readable date range text
699 *
700 * @return string
701 */
702 public function getDateRangeText(): string
703 {
704 $format = get_option('date_format');
705 return date_i18n($format, $this->startDate) . ' - ' . date_i18n($format, $this->endDate);
706 }
707
708 /**
709 * Calculate percentage change between two values
710 *
711 * @param float|int $current
712 * @param float|int $previous
713 * @return string
714 */
715 private function calculateChange($current, $previous): string
716 {
717 if ($previous == 0) {
718 if ($current > 0) {
719 return '+100%';
720 }
721 return '0%';
722 }
723
724 $change = (($current - $previous) / $previous) * 100;
725 $prefix = $change >= 0 ? '+' : '';
726 return $prefix . round($change, 1) . '%';
727 }
728
729 /**
730 * Format bytes to human readable
731 *
732 * @param int $bytes
733 * @return string
734 */
735 private function formatBytes(int $bytes): string
736 {
737 $units = ['B', 'KB', 'MB', 'GB', 'TB'];
738 $bytes = max($bytes, 0);
739 $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
740 $pow = min($pow, count($units) - 1);
741 $bytes /= pow(1024, $pow);
742
743 return round($bytes, 2) . ' ' . $units[$pow];
744 }
745
746 /**
747 * Get start date timestamp
748 *
749 * @return int
750 */
751 public function getStartDate(): int
752 {
753 return $this->startDate;
754 }
755
756 /**
757 * Get end date timestamp
758 *
759 * @return int
760 */
761 public function getEndDate(): int
762 {
763 return $this->endDate;
764 }
765 }
766