PluginProbe ʕ •ᴥ•ʔ
Download Manager / trunk
Download Manager vtrunk
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 / Admin / views / stats / history.php
download-manager / src / Admin / views / stats Last commit date
history.php 1 week ago
history.php
1130 lines
1 <?php
2 global $wpdb;
3 if ( ! defined( "ABSPATH" ) ) {
4 die( "Shit happens!" );
5 }
6 $get_params = $_GET;
7
8 // Tables used
9 $states_table = "{$wpdb->prefix}ahm_download_stats";
10 $posts_table = "{$wpdb->prefix}posts";
11 $users_table = "{$wpdb->base_prefix}users";
12
13 /**
14 * Query Parameters
15 */
16 $from_date_string = wpdm_query_var( 'from_date' );
17 $to_date_string = wpdm_query_var( 'to_date' );
18 $user_ids = wpdm_query_var( 'user_ids' ) ?: [];
19 $package_ids = wpdm_query_var( 'package_ids' ) ?: [];
20 $page_no = wpdm_query_var( 'page_no', 'int' ) ?: 1;
21
22 /**
23 * Sanitize parameters
24 */
25 $from_date_string = sanitize_text_field( $from_date_string );
26 $to_date_string = sanitize_text_field( $to_date_string );
27 $user_ids = \WPDM\__\__::sanitize_array( $user_ids, 'int' );
28 $package_ids = \WPDM\__\__::sanitize_array( $package_ids, 'int' );
29
30 /**
31 * Selected/Initial Values for the fields
32 * These are necessary for initialize filter fields
33 */
34
35 $min_timestamp = $wpdb->get_var( "SELECT min(timestamp) from $states_table" ) ?: time();
36 $selected_from_date = $from_date_string ? new DateTime( $from_date_string ) : ( new DateTime() )->setTimestamp( $min_timestamp );
37
38 $selected_to_date = $to_date_string ? ( new DateTime( $to_date_string ) ) : ( new DateTime() )->setTimestamp( time() );
39 $selected_to_date->modify( 'tomorrow' ); // we need to get the timestamp of the end of selected to_date
40 $end_of_selected_to_date_timestamp = $selected_to_date->getTimestamp() - 1;
41 $selected_to_date->setTimestamp( $end_of_selected_to_date_timestamp );
42
43
44 $user_ids_string = implode( ',', $user_ids );
45 $selected_users = [];
46 if ( ! empty( $user_ids_string ) ) {
47 $selected_users = $wpdb->get_results( "SELECT ID, user_login, display_name, user_email FROM $users_table WHERE ID IN ({$user_ids_string})" );
48 }
49
50 $package_ids_string = implode( ',', $package_ids );
51 $selected_packages = [];
52 if ( ! empty( $package_ids_string ) ) {
53 $selected_packages = $wpdb->get_results( "SELECT ID, post_title FROM $posts_table WHERE ID IN ({$package_ids_string})" );
54 }
55
56
57 /**
58 * Filter query parts
59 */
60
61 $timestamp_filter = " AND s.timestamp >= {$selected_from_date->getTimestamp()} AND s.timestamp <= {$selected_to_date->getTimestamp()}";
62
63 $user_ids_filter = "";
64 if ( count( $user_ids ) > 0 ) {
65 $user_ids_filter = " AND s.uid IN (" . $user_ids_string . ") ";
66 }
67
68 $package_ids_filter = "";
69 if ( count( $package_ids ) > 0 ) {
70 $package_ids_filter = " AND s.pid IN (" . $package_ids_string . ") ";
71 }
72
73 $uniqq = "";
74 if ( wpdm_query_var( 'uniq' ) ) {
75 $uniqq = " group by pid ";
76 }
77
78
79 /**
80 * Statistics query
81 */
82
83 $items_per_page = 30;
84 $start = $page_no ? ( $page_no - 1 ) * $items_per_page : 0;
85
86 //pd($selected_from_date->getTimestamp());
87 if(wpdm_query_var('cats')) {
88 $cats_filter = get_posts( [
89 'post_type' => 'wpdmpro',
90 'post_status' => 'publish',
91 'numberposts' => - 1,
92 'tax_query' => [
93 [
94 'taxonomy' => 'wpdmcategory',
95 'field' => 'term_id',
96 'terms' => wpdm_query_var( 'cats' ),
97 'include_children' => false
98 ]
99 ]
100 ] );
101 $pack_ids = [0];
102 foreach ( $cats_filter as $pac ) {
103 $pack_ids[] = $pac->ID;
104 }
105 }
106 $pid_query = '';
107 if(isset($pack_ids) && is_array($pack_ids) && count($pack_ids) > 0)
108 $pid_query = ' and s.pid in ('.implode(',', $pack_ids).')';
109
110 $hash = \WPDM\__\Crypt::encrypt( "SELECT [##fields##] FROM $states_table s WHERE 1 {$package_ids_filter} {$user_ids_filter} {$timestamp_filter} {$pid_query} {$uniqq}" );
111
112 $count_downloads_without_paging = $wpdb->get_var( "SELECT count(s.pid) FROM $states_table s, $posts_table p WHERE s.pid = p.ID
113 {$package_ids_filter} {$user_ids_filter} {$timestamp_filter} {$pid_query}
114 " );
115
116 $filtered_result_rows = $wpdb->get_results( "SELECT p.post_title, s.* FROM $states_table s, $posts_table p WHERE s.pid = p.ID
117 {$package_ids_filter} {$user_ids_filter} {$timestamp_filter} {$pid_query} {$uniqq}
118 order by `timestamp` desc limit $start, $items_per_page
119 " );
120 /*wpdmprecho("SELECT p.post_title, s.* FROM $states_table s, $posts_table p WHERE s.pid = p.ID
121 {$package_ids_filter} {$user_ids_filter} {$timestamp_filter} {$pid_query} {$uniqq}
122 order by `timestamp` desc limit $start, $items_per_page
123 ");*/
124 $pagination = array(
125 'base' => @add_query_arg( 'page_no', '%#%' ),
126 'format' => '',
127 'total' => ceil( $count_downloads_without_paging / $items_per_page ),
128 'current' => $page_no,
129 'show_all' => false,
130 'type' => 'list',
131 'prev_next' => true,
132 'prev_text' => '<i class="icon icon-angle-left"></i> Previous',
133 'next_text' => 'Next <i class="icon icon-angle-right"></i>',
134 );
135
136 ?>
137
138 <!-- Elegant Filters Section -->
139 <div class="wpdm-history-filters">
140 <form method="get" action="<?php echo admin_url( 'edit.php' ); ?>" class="elegant-filter-form">
141 <input type="hidden" name="post_type" value="wpdmpro"/>
142 <input type="hidden" name="page" value="wpdm-stats"/>
143 <input type="hidden" name="type" value="history"/>
144 <input type="hidden" name="filter" value="1"/>
145
146 <!-- Filter Header -->
147 <div class="filter-header">
148 <div class="filter-title">
149 <i class="fas fa-filter"></i>
150 <h3><?php _e('Advanced Filters', 'download-manager'); ?></h3>
151 </div>
152 <div class="filter-summary">
153 <?php if (!empty($user_ids) || !empty($package_ids) || isset($cats) || $from_date_string || $to_date_string): ?>
154 <span class="active-filters-badge">
155 <i class="fas fa-check-circle"></i>
156 <?php _e('Filters Applied', 'download-manager'); ?>
157 </span>
158 <?php endif; ?>
159 </div>
160 </div>
161
162 <!-- Quick Date Presets -->
163 <div class="quick-filters-section">
164 <div class="quick-filters-title">
165 <i class="fas fa-clock"></i>
166 <?php _e('Quick Date Ranges', 'download-manager'); ?>
167 </div>
168 <div class="quick-filter-buttons">
169 <button type="button" class="quick-filter-btn" data-days="1">
170 <i class="fas fa-calendar-day"></i>
171 <?php _e('Today', 'download-manager'); ?>
172 </button>
173 <button type="button" class="quick-filter-btn" data-days="7">
174 <i class="fas fa-calendar-week"></i>
175 <?php _e('Last 7 Days', 'download-manager'); ?>
176 </button>
177 <button type="button" class="quick-filter-btn" data-days="30">
178 <i class="fas fa-calendar-alt"></i>
179 <?php _e('Last 30 Days', 'download-manager'); ?>
180 </button>
181 <button type="button" class="quick-filter-btn" data-days="90">
182 <i class="fas fa-calendar"></i>
183 <?php _e('Last 90 Days', 'download-manager'); ?>
184 </button>
185 </div>
186 </div>
187
188 <!-- Main Filters Grid -->
189 <div class="filters-grid">
190 <!-- Date Range Section -->
191 <div class="filter-section date-section">
192 <div class="section-header">
193 <i class="fas fa-calendar-alt"></i>
194 <h4><?php _e('Date Range', 'download-manager'); ?></h4>
195 </div>
196 <div class="date-inputs">
197 <div class="date-input-group">
198 <input type="text" name="from_date" value="<?php echo $selected_from_date->format( 'Y-m-d' ) ?>"
199 class="datepicker form-control" readonly="readonly"
200 aria-label="<?php esc_attr_e('From date', 'download-manager'); ?>"/>
201 </div>
202 <div class="date-input-group">
203 <input type="text" name="to_date" value="<?php echo $selected_to_date->format( 'Y-m-d' ) ?>"
204 class="datepicker form-control" readonly="readonly"
205 aria-label="<?php esc_attr_e('To date', 'download-manager'); ?>"/>
206 </div>
207 </div>
208 </div>
209
210 <!-- Users Filter Section -->
211 <div class="filter-section">
212 <div class="section-header">
213 <i class="fas fa-users"></i>
214 <h4><?php _e('Users', 'download-manager'); ?></h4>
215 <?php if (!empty($user_ids)): ?>
216 <div class="clear-filter">
217 <?php
218 $get_params_xu = $get_params;
219 unset($get_params_xu['user_ids']);
220 $reset_url = add_query_arg($get_params_xu, 'edit.php');
221 ?>
222 <a href="<?php echo esc_url($reset_url); ?>" class="clear-btn" title="<?php _e('Clear user filter', 'download-manager'); ?>">
223 <i class="fas fa-times"></i>
224 </a>
225 </div>
226 <?php endif; ?>
227 </div>
228 <div class="select-wrapper">
229 <select id="user_ids" name="user_ids[]" multiple="multiple" class="elegant-select">
230 <?php foreach ($selected_users as $u): ?>
231 <option selected value="<?php echo $u->ID ?>">
232 <?php echo $u->display_name . " (" . $u->user_login . ")"; ?>
233 </option>
234 <?php endforeach ?>
235 </select>
236 </div>
237 </div>
238
239 <!-- Categories Filter Section -->
240 <?php
241 $cats = wpdm_query_var( 'cats' );
242 $cats = is_array($cats) ? array_map('intval', $cats) : [];
243 function printOptions($options, $level = 0, $selected_cats = []) {
244 $pstr = $level > 0 ? str_pad(" ", $level*2+1, "", STR_PAD_LEFT) : '';
245 foreach ($options as $option) {
246 $selected = in_array($option['category']->term_id, $selected_cats) ? 'selected' : '';
247 echo "<option value='{$option['category']->term_id}' {$selected}>{$pstr}{$option['category']->name}</option>";
248 if(count($option['childs']) > 0) {
249 printOptions($option['childs'], $level + 1, $selected_cats);
250 }
251 }
252 }
253 ?>
254 <div class="filter-section">
255 <div class="section-header">
256 <i class="fas fa-folder"></i>
257 <h4><?php _e('Categories', 'download-manager'); ?></h4>
258 <?php if (!empty($cats)): ?>
259 <div class="clear-filter">
260 <?php
261 $get_params_xc = $get_params;
262 unset($get_params_xc['cats']);
263 $reset_url = add_query_arg($get_params_xc, 'edit.php');
264 ?>
265 <a href="<?php echo esc_url($reset_url); ?>" class="clear-btn" title="<?php _e('Clear category filter', 'download-manager'); ?>">
266 <i class="fas fa-times"></i>
267 </a>
268 </div>
269 <?php endif; ?>
270 </div>
271 <div class="select-wrapper">
272 <select id="cats" name="cats[]" multiple="multiple" class="elegant-select">
273 <?php
274 $cats_options = WPDM()->categories->hArray();
275 printOptions($cats_options, 0, $cats);
276 ?>
277 </select>
278 </div>
279 </div>
280
281 <!-- Packages Filter Section -->
282 <div class="filter-section">
283 <div class="section-header">
284 <i class="fas fa-box"></i>
285 <h4><?php _e('Packages', 'download-manager'); ?></h4>
286 <?php if (!empty($package_ids)): ?>
287 <div class="clear-filter">
288 <?php
289 $get_params_xp = $get_params;
290 unset($get_params_xp['package_ids']);
291 $reset_url = add_query_arg($get_params_xp, 'edit.php');
292 ?>
293 <a href="<?php echo esc_url($reset_url); ?>" class="clear-btn" title="<?php _e('Clear package filter', 'download-manager'); ?>">
294 <i class="fas fa-times"></i>
295 </a>
296 </div>
297 <?php endif; ?>
298 </div>
299 <div class="select-wrapper">
300 <select id="package_ids" name="package_ids[]" multiple="multiple" class="elegant-select">
301 <?php foreach ($selected_packages as $p): ?>
302 <option selected value="<?php echo $p->ID ?>">
303 <?php echo esc_html($p->post_title); ?>
304 </option>
305 <?php endforeach ?>
306 </select>
307 </div>
308 </div>
309 </div>
310
311 <!-- Action Buttons -->
312 <div class="filter-actions">
313 <button type="submit" class="btn btn-primary">
314 <i class="fas fa-search"></i>
315 <?php _e('Apply Filters', 'download-manager'); ?>
316 </button>
317 <a href="edit.php?post_type=wpdmpro&page=wpdm-stats&task=export&__xnonce=<?php echo wp_create_nonce( NONCE_KEY ); ?>&hash=<?php echo $hash; ?>"
318 class="btn btn-secondary">
319 <i class="fas fa-file-export"></i>
320 <?php _e('Export Results', 'download-manager'); ?>
321 </a>
322 <a href="edit.php?post_type=wpdmpro&page=wpdm-stats&type=history"
323 class="btn btn-secondary">
324 <i class="fas fa-undo"></i>
325 <?php _e('Reset All', 'download-manager'); ?>
326 </a>
327 </div>
328 </form>
329 </div>
330 <hr/>
331 <?php if ( $count_downloads_without_paging ) : ?>
332
333 <!-- Results Header -->
334 <div class="results-header">
335 <div class="results-info">
336 <div class="results-count">
337 <i class="fas fa-list-ul"></i>
338 <span><?php printf(__('Showing %s - %s of %s downloads', 'download-manager'),
339 '<strong>' . number_format($start + 1) . '</strong>',
340 '<strong>' . number_format($start + count( $filtered_result_rows )) . '</strong>',
341 '<strong>' . number_format($count_downloads_without_paging) . '</strong>'
342 ); ?></span>
343 </div>
344 <?php if ($count_downloads_without_paging > $items_per_page): ?>
345 <div class="results-pagination-info">
346 <i class="fas fa-file-alt"></i>
347 <?php printf(__('Page %d of %d', 'download-manager'),
348 $page_no,
349 ceil($count_downloads_without_paging / $items_per_page)
350 ); ?>
351 </div>
352 <?php endif; ?>
353 </div>
354 <div class="results-actions">
355 <div class="view-toggle">
356 <button class="view-btn active" data-view="table">
357 <i class="fas fa-table"></i>
358 <?php _e('Table', 'download-manager'); ?>
359 </button>
360 <button class="view-btn" data-view="cards">
361 <i class="fas fa-th-large"></i>
362 <?php _e('Cards', 'download-manager'); ?>
363 </button>
364 </div>
365 </div>
366 </div>
367
368 <!-- Enhanced Results Table -->
369 <div class="elegant-table-container">
370 <div class="table-wrapper">
371 <table class="elegant-table">
372 <thead>
373 <tr>
374 <th class="sortable" data-sort="package">
375 <div class="th-content">
376 <i class="fas fa-box"></i>
377 <span><?php _e( "Package", "download-manager" ); ?></span>
378 <i class="fas fa-sort sort-icon"></i>
379 </div>
380 </th>
381 <th class="sortable" data-sort="file">
382 <div class="th-content">
383 <i class="fas fa-file"></i>
384 <span><?php _e( "File", "download-manager" ); ?></span>
385 <i class="fas fa-sort sort-icon"></i>
386 </div>
387 </th>
388 <th class="sortable" data-sort="version">
389 <div class="th-content">
390 <i class="fas fa-tag"></i>
391 <span><?php _e( "Version", "download-manager" ); ?></span>
392 <i class="fas fa-sort sort-icon"></i>
393 </div>
394 </th>
395 <th class="sortable" data-sort="time">
396 <div class="th-content">
397 <i class="fas fa-clock"></i>
398 <span><?php _e( "Date & Time", "download-manager" ); ?></span>
399 <i class="fas fa-sort sort-icon"></i>
400 </div>
401 </th>
402 <th class="sortable" data-sort="user">
403 <div class="th-content">
404 <i class="fas fa-user"></i>
405 <span><?php _e( "User", "download-manager" ); ?></span>
406 <i class="fas fa-sort sort-icon"></i>
407 </div>
408 </th>
409 <th>
410 <div class="th-content">
411 <i class="fas fa-globe"></i>
412 <span><?php _e( "Location", "download-manager" ); ?></span>
413 </div>
414 </th>
415 <th>
416 <div class="th-content">
417 <i class="fas fa-desktop"></i>
418 <span><?php _e( "System", "download-manager" ); ?></span>
419 </div>
420 </th>
421 </tr>
422 </thead>
423 <tbody>
424 <?php
425 // Add country detection function
426 if (!function_exists('wpdm_get_country_from_ip_history')) {
427 function wpdm_get_country_from_ip_history($ip) {
428 if (empty($ip) || $ip === '0.0.0.0') return 'Unknown';
429
430 if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false) {
431 return 'Local/Private';
432 }
433
434 $cache_key = 'wpdm_country_hist_' . md5($ip);
435 $cached_country = get_transient($cache_key);
436
437 if ($cached_country !== false) {
438 return $cached_country;
439 }
440
441 $response = wp_remote_get("https://ipapi.co/{$ip}/country_name/", array(
442 'timeout' => 3,
443 'headers' => array('User-Agent' => 'WordPress Download Manager History')
444 ));
445
446 if (!is_wp_error($response) && wp_remote_retrieve_response_code($response) == 200) {
447 $country = trim(wp_remote_retrieve_body($response));
448 if (!empty($country) && !strpos($country, 'error')) {
449 set_transient($cache_key, $country, 12 * HOUR_IN_SECONDS);
450 return $country;
451 }
452 }
453
454 // Fallback to basic detection
455 $ip_parts = explode('.', $ip);
456 if (count($ip_parts) >= 2) {
457 $first_octet = intval($ip_parts[0]);
458 if (in_array($first_octet, [3, 4, 6, 7, 8, 9, 11, 13, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 26, 28, 29, 30, 32, 33, 34, 35, 38, 40, 44, 45, 47, 48, 50, 52, 54, 55, 56, 57, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 96, 97, 98, 99, 100, 104, 107, 108, 173, 174, 184, 199, 204, 205, 206, 207, 208, 209, 216])) {
459 set_transient($cache_key, 'United States', 12 * HOUR_IN_SECONDS);
460 return 'United States';
461 }
462 }
463
464 set_transient($cache_key, 'Unknown', 12 * HOUR_IN_SECONDS);
465 return 'Unknown';
466 }
467 }
468
469 if (!function_exists('wpdm_stats_browser_icon')) {
470 function wpdm_stats_browser_icon($name) {
471 $name = strtolower($name);
472 $map = [
473 'edge' => ['fab', 'fa-edge'],
474 'chrome' => ['fab', 'fa-chrome'],
475 'firefox' => ['fab', 'fa-firefox-browser'],
476 'safari' => ['fab', 'fa-safari'],
477 'opera' => ['fab', 'fa-opera'],
478 'internet explorer' => ['fab', 'fa-internet-explorer'],
479 ];
480 foreach ($map as $needle => $icon) {
481 if (strpos($name, $needle) !== false) {
482 return $icon;
483 }
484 }
485 return ['fas', 'fa-globe'];
486 }
487 }
488
489 foreach ( $filtered_result_rows as $stat ) {
490 $agent = WPDM()->userAgent->set( $stat->agent )->parse();
491 $display_name = 'Guest';
492 $user_class = 'guest-user';
493
494 // Get country name from IP.
495 // Only perform the external geolocation lookup when IP handling is enabled
496 // (same gate as the display below), so stored IPs are never sent to a third
497 // party when the admin has disabled IP handling via the __wpdm_noip option.
498 $country_name = __('Unknown', 'download-manager');
499 if ( get_option( '__wpdm_noip' ) == 0 && !empty($stat->ip) ) {
500 $country_name = wpdm_get_country_from_ip_history($stat->ip);
501 }
502
503 if ( $stat->uid > 0 ) {
504 $user = get_user_by( 'id', $stat->uid );
505 if ( is_object( $user ) ) {
506 $display_name = $user->display_name;
507 $user_class = 'registered-user';
508 } else {
509 $display_name = __('[ Deleted User ]', 'download-manager');
510 $user_class = 'deleted-user';
511 }
512 }
513 ?>
514 <tr class="elegant-row" data-download-id="<?php echo $stat->id; ?>">
515 <!-- Package -->
516 <td class="package-cell">
517 <div class="package-info">
518 <div class="package-main">
519 <a title="<?php _e('Filter by this package', 'download-manager'); ?>"
520 class="package-link"
521 href="edit.php?post_type=wpdmpro&page=wpdm-stats&type=history&package_ids[]=<?php echo $stat->pid ?>">
522 <span class="package-title"><?php echo esc_html($stat->post_title); ?></span>
523 </a>
524 </div>
525 <div class="package-actions">
526 <a target="_blank" href="post.php?action=edit&post=<?php echo $stat->pid; ?>"
527 title="<?php _e('Edit Package', 'download-manager'); ?>" class="action-link edit-link">
528 <i class="fas fa-edit"></i>
529 </a>
530 <a target="_blank" href="<?php echo get_permalink( $stat->pid ); ?>"
531 title="<?php _e('View Package', 'download-manager'); ?>" class="action-link view-link">
532 <i class="fas fa-external-link-alt"></i>
533 </a>
534 </div>
535 </div>
536 </td>
537
538 <!-- File -->
539 <td class="file-cell">
540 <div class="file-info">
541 <i class="fas fa-file-alt file-icon"></i>
542 <span class="filename" title="<?php echo esc_attr($stat->filename); ?>">
543 <?php echo esc_html(strlen($stat->filename) > 25 ? substr($stat->filename, 0, 25) . '...' : $stat->filename); ?>
544 </span>
545 </div>
546 </td>
547
548 <!-- Version -->
549 <td class="version-cell">
550 <?php if (!empty($stat->version)): ?>
551 <span class="version-badge"><?php echo esc_html($stat->version); ?></span>
552 <?php else: ?>
553 <span class="no-version"></span>
554 <?php endif; ?>
555 </td>
556
557 <!-- Date & Time -->
558 <td class="datetime-cell">
559 <div class="datetime-info">
560 <span class="dt-date"><?php echo wp_date( get_option( 'date_format' ), $stat->timestamp ); ?></span>
561 <span class="dt-meta"><?php echo wp_date( get_option( 'time_format' ), $stat->timestamp ); ?> &middot; <?php echo human_time_diff($stat->timestamp, current_time('timestamp')) . ' ' . __('ago', 'download-manager'); ?></span>
562 </div>
563 </td>
564
565 <!-- User -->
566 <td class="user-cell">
567 <div class="user-info <?php echo $user_class; ?>">
568 <?php if ( $stat->uid > 0 ): ?>
569 <span class="user-avatar">
570 <i class="fas fa-user"></i>
571 </span>
572 <span class="user-main">
573 <a title="<?php _e('Filter by this user', 'download-manager'); ?>"
574 class="user-link"
575 href="edit.php?post_type=wpdmpro&page=wpdm-stats&type=history&filter=1&user_ids[0]=<?php echo $stat->uid; ?>">
576 <span class="user-name"><?php echo esc_html($display_name); ?></span>
577 </a>
578 <a target="_blank" title="<?php _e('Edit User', 'download-manager'); ?>"
579 class="user-edit" href="user-edit.php?user_id=<?php echo $stat->uid; ?>">
580 <i class="fas fa-pen"></i>
581 </a>
582 </span>
583 <?php else: ?>
584 <span class="user-avatar guest-avatar">
585 <i class="fas fa-user-slash"></i>
586 </span>
587 <span class="user-main">
588 <span class="user-name guest-name"><?php _e('Guest', 'download-manager'); ?></span>
589 </span>
590 <?php endif; ?>
591 </div>
592 </td>
593
594 <!-- Location -->
595 <td class="location-cell">
596 <?php if ( get_option( '__wpdm_noip' ) == 0 && !empty($stat->ip) ): ?>
597 <div class="location-info">
598 <span class="country-info">
599 <i class="fas fa-flag"></i>
600 <span class="country-name"><?php echo esc_html($country_name); ?></span>
601 </span>
602 <a target="_blank" href="https://ip-api.com/#<?php echo esc_attr($stat->ip); ?>"
603 class="ip-link" title="<?php _e('View IP location details', 'download-manager'); ?>">
604 <span class="ip-address"><?php echo esc_html($stat->ip); ?></span>
605 </a>
606 </div>
607 <?php else: ?>
608 <div class="location-unknown">
609 <i class="fas fa-question-circle"></i>
610 <span><?php _e('Unknown', 'download-manager'); ?></span>
611 </div>
612 <?php endif; ?>
613 </td>
614
615 <!-- System -->
616 <td class="system-cell">
617 <?php $bicon = wpdm_stats_browser_icon($agent->browserName); ?>
618 <div class="system-info">
619 <span class="browser-info">
620 <i class="<?php echo $bicon[0]; ?> <?php echo $bicon[1]; ?> browser-icon"></i>
621 <span class="browser-name"><?php echo esc_html($agent->browserName); ?></span>
622 </span>
623 <span class="os-info"><?php echo esc_html($agent->OS); ?></span>
624 </div>
625 </td>
626
627 </tr>
628 <?php
629 }
630 ?>
631
632 </tbody>
633 </table>
634 </div>
635
636 <!-- Cards View (Hidden by default) -->
637 <div class="cards-wrapper">
638 <?php
639 foreach ( $filtered_result_rows as $stat ) {
640 $agent = WPDM()->userAgent->set( $stat->agent )->parse();
641 $display_name = 'Guest';
642 $user_class = 'guest-user';
643
644 // Get country name from IP for cards view
645 $country_name = wpdm_get_country_from_ip_history($stat->ip);
646
647 if ( $stat->uid > 0 ) {
648 $user = get_user_by( 'id', $stat->uid );
649 if ( is_object( $user ) ) {
650 $display_name = $user->display_name;
651 $user_class = 'registered-user';
652 } else {
653 $display_name = __('[ Deleted User ]', 'download-manager');
654 $user_class = 'deleted-user';
655 }
656 }
657 ?>
658 <div class="download-card" data-download-id="<?php echo $stat->id; ?>">
659 <div class="card-header">
660 <div class="card-package-info">
661 <h4 class="card-package-title">
662 <a href="edit.php?post_type=wpdmpro&page=wpdm-stats&type=history&package_ids[]=<?php echo $stat->pid ?>"
663 title="<?php _e('Filter by this package', 'download-manager'); ?>">
664 <?php echo esc_html($stat->post_title); ?>
665 </a>
666 </h4>
667 </div>
668 <div class="card-package-actions">
669 <a target="_blank" href="post.php?action=edit&post=<?php echo $stat->pid; ?>"
670 title="<?php _e('Edit Package', 'download-manager'); ?>" class="card-action-link">
671 <i class="fas fa-edit"></i>
672 </a>
673 <a target="_blank" href="<?php echo get_permalink( $stat->pid ); ?>"
674 title="<?php _e('View Package', 'download-manager'); ?>" class="card-action-link">
675 <i class="fas fa-external-link-alt"></i>
676 </a>
677 </div>
678 </div>
679
680 <div class="card-body">
681 <!-- File -->
682 <div class="card-field">
683 <div class="card-field-label">
684 <i class="fas fa-file-alt"></i>
685 <?php _e('File', 'download-manager'); ?>
686 </div>
687 <div class="card-field-value">
688 <span class="card-file-name" title="<?php echo esc_attr($stat->filename); ?>">
689 <?php echo esc_html(strlen($stat->filename) > 30 ? substr($stat->filename, 0, 30) . '...' : $stat->filename); ?>
690 </span>
691 </div>
692 </div>
693
694 <!-- Version -->
695 <div class="card-field">
696 <div class="card-field-label">
697 <i class="fas fa-tag"></i>
698 <?php _e('Version', 'download-manager'); ?>
699 </div>
700 <div class="card-field-value">
701 <?php if (!empty($stat->version)): ?>
702 <span class="card-version-badge"><?php echo esc_html($stat->version); ?></span>
703 <?php else: ?>
704 <span class="no-version"></span>
705 <?php endif; ?>
706 </div>
707 </div>
708
709 <!-- Date & Time -->
710 <div class="card-field">
711 <div class="card-field-label">
712 <i class="fas fa-calendar-alt"></i>
713 <?php _e('Date & Time', 'download-manager'); ?>
714 </div>
715 <div class="card-field-value">
716 <div class="card-datetime">
717 <div class="card-date">
718 <?php echo wp_date( get_option( 'date_format' ), $stat->timestamp ); ?>
719 </div>
720 <div class="card-time">
721 <?php echo wp_date( get_option( 'time_format' ), $stat->timestamp ); ?>
722 </div>
723 <div class="card-relative-time">
724 <?php echo human_time_diff($stat->timestamp, current_time('timestamp')) . ' ' . __('ago', 'download-manager'); ?>
725 </div>
726 </div>
727 </div>
728 </div>
729
730 <!-- User -->
731 <div class="card-field">
732 <div class="card-field-label">
733 <i class="fas fa-user"></i>
734 <?php _e('User', 'download-manager'); ?>
735 </div>
736 <div class="card-field-value">
737 <div class="card-user-info <?php echo $user_class; ?>">
738 <?php if ( $stat->uid > 0 ): ?>
739 <div class="card-user-avatar">
740 <i class="fas fa-user"></i>
741 </div>
742 <a href="edit.php?post_type=wpdmpro&page=wpdm-stats&type=history&filter=1&user_ids[0]=<?php echo $stat->uid; ?>"
743 title="<?php _e('Filter by this user', 'download-manager'); ?>" class="card-user-name">
744 <?php echo esc_html($display_name); ?>
745 </a>
746 <?php else: ?>
747 <div class="card-user-avatar">
748 <i class="fas fa-user-slash"></i>
749 </div>
750 <span class="card-user-name"><?php _e('Guest', 'download-manager'); ?></span>
751 <?php endif; ?>
752 </div>
753 </div>
754 </div>
755
756 <!-- Location -->
757 <div class="card-field">
758 <div class="card-field-label">
759 <i class="fas fa-globe"></i>
760 <?php _e('Location', 'download-manager'); ?>
761 </div>
762 <div class="card-field-value">
763 <?php if ( get_option( '__wpdm_noip' ) == 0 && !empty($stat->ip) ): ?>
764 <div class="card-location-info">
765 <div class="card-country">
766 <i class="fas fa-flag"></i>
767 <span><?php echo esc_html($country_name); ?></span>
768 </div>
769 <div class="card-ip">
770 <a target="_blank" href="https://ip-api.com/#<?php echo esc_attr($stat->ip); ?>"
771 class="card-location-link" title="<?php _e('View IP location details', 'download-manager'); ?>">
772 <i class="fas fa-globe"></i>
773 <?php echo esc_html($stat->ip); ?>
774 </a>
775 </div>
776 </div>
777 <?php else: ?>
778 <span><?php _e('Unknown', 'download-manager'); ?></span>
779 <?php endif; ?>
780 </div>
781 </div>
782
783 <!-- System -->
784 <div class="card-field">
785 <div class="card-field-label">
786 <i class="fas fa-desktop"></i>
787 <?php _e('System', 'download-manager'); ?>
788 </div>
789 <div class="card-field-value">
790 <?php $bicon = wpdm_stats_browser_icon($agent->browserName); ?>
791 <div class="card-system-info">
792 <div class="card-browser">
793 <i class="<?php echo $bicon[0]; ?> <?php echo $bicon[1]; ?>"></i>
794 <span><?php echo esc_html($agent->browserName); ?></span>
795 </div>
796 <div class="card-os">
797 <i class="fas fa-desktop"></i>
798 <span><?php echo esc_html($agent->OS); ?></span>
799 </div>
800 </div>
801 </div>
802 </div>
803 </div>
804 </div>
805 <?php
806 }
807 ?>
808 </div>
809
810 <!-- Elegant Pagination -->
811 <div class="table-footer">
812 <div class="pagination-wrapper">
813 <?php if ( ! empty( $wp_query->query_vars['s'] ) ) {
814 $pagination['add_args'] = array( 's' => get_query_var( 's' ) );
815 }
816
817 $pagination_html = paginate_links( $pagination );
818 if ($pagination_html):
819 // Clean up and style the pagination HTML
820 $styled_pagination = $pagination_html;
821
822 // Replace the main ul class
823 $styled_pagination = str_replace(
824 ['<ul class=\'page-numbers\'>', '<ul class="page-numbers">'],
825 '<ul class="pagination-list">',
826 $styled_pagination
827 );
828
829 // Replace all page-numbers classes with pagination-item
830 $styled_pagination = str_replace(
831 ['class="page-numbers', 'class=\'page-numbers'],
832 'class="pagination-item',
833 $styled_pagination
834 );
835
836 // Fix the next/prev buttons - they should have proper classes
837 $styled_pagination = str_replace(
838 ['<a class="next pagination-item"', '<a class="prev pagination-item"'],
839 ['<a class="pagination-item next"', '<a class="pagination-item prev"'],
840 $styled_pagination
841 );
842
843 // Fix dots styling
844 $styled_pagination = str_replace(
845 '<span class="pagination-item dots">',
846 '<span class="pagination-item dots">',
847 $styled_pagination
848 );
849
850 echo '<div class="elegant-pagination">' . $styled_pagination . '</div>';
851 endif; ?>
852 </div>
853
854 <div class="table-info">
855 <div class="items-per-page">
856 <i class="fas fa-list"></i>
857 <?php printf(__('%d items per page', 'download-manager'), $items_per_page); ?>
858 </div>
859 <div class="total-items">
860 <i class="fas fa-database"></i>
861 <?php printf(__('%s total downloads', 'download-manager'), number_format($count_downloads_without_paging)); ?>
862 </div>
863 </div>
864 </div>
865 </div>
866
867 <?php else : ?>
868 <div class="no-results-container">
869 <div class="no-results-content">
870 <div class="no-results-icon">
871 <i class="fas fa-search"></i>
872 </div>
873 <div class="no-results-message">
874 <h4><?php _e('No Downloads Found', 'download-manager'); ?></h4>
875 <p><?php _e('No download records match your current filters. Try adjusting your search criteria or date range.', 'download-manager'); ?></p>
876 </div>
877 <div class="no-results-actions">
878 <a href="edit.php?post_type=wpdmpro&page=wpdm-stats&type=history" class="btn-elegant secondary">
879 <i class="fas fa-undo"></i>
880 <?php _e('Reset Filters', 'download-manager'); ?>
881 </a>
882 <a href="edit.php?post_type=wpdmpro&page=wpdm-stats" class="btn-elegant primary">
883 <i class="fas fa-chart-bar"></i>
884 <?php _e('View Overview', 'download-manager'); ?>
885 </a>
886 </div>
887 </div>
888 </div>
889 <?php endif; ?>
890
891 <!-- SCRIPTS -->
892
893 <link href="<?php echo esc_url( WPDM_BASE_URL . 'assets/select2/css/select2.min.css' ); ?>" rel="stylesheet"/>
894 <script src="<?php echo esc_url( WPDM_BASE_URL . 'assets/select2/js/select2.min.js' ); ?>"></script>
895 <script>
896 jQuery(function ($) {
897 // Standard jQuery UI datepicker (default style)
898 $(".datepicker").datepicker({
899 dateFormat: 'yy-mm-dd',
900 minDate: new Date(<?= intval( $min_timestamp ) * 1000 ?>),
901 maxDate: new Date(),
902 changeMonth: true,
903 changeYear: true,
904 yearRange: "-20:+0",
905 showOtherMonths: true,
906 selectOtherMonths: true
907 });
908
909 // Initialize Select2 components
910 function initializeSelect2() {
911 // Categories Select2
912 if ($('#cats').length && !$('#cats').hasClass('select2-hidden-accessible')) {
913 $('#cats').select2({
914 placeholder: "<?php _e('Select categories...', 'download-manager'); ?>",
915 allowClear: true,
916 theme: "default",
917 width: '100%'
918 });
919 }
920
921 // Packages Select2
922 if ($('#package_ids').length && !$('#package_ids').hasClass('select2-hidden-accessible')) {
923 $('#package_ids').select2({
924 theme: "default",
925 placeholder: "<?php _e('Search packages...', 'download-manager'); ?>",
926 allowClear: true,
927 width: '100%',
928 ajax: {
929 url: ajaxurl,
930 dataType: 'json',
931 delay: 250,
932 data: function (params) {
933 return {
934 action: 'wpdm_stats_get_packages',
935 term: params.term,
936 __spnonce: '<?php echo wp_create_nonce(WPDM_PUB_NONCE); ?>'
937 };
938 },
939 processResults: function (data) {
940 if (data.success === false) {
941 return { results: [] };
942 }
943 return data;
944 },
945 cache: true
946 },
947 minimumInputLength: 2,
948 escapeMarkup: function (markup) { return markup; }
949 });
950 }
951
952 // Users Select2
953 if ($('#user_ids').length && !$('#user_ids').hasClass('select2-hidden-accessible')) {
954 $('#user_ids').select2({
955 theme: "default",
956 placeholder: "<?php _e('Search users...', 'download-manager'); ?>",
957 allowClear: true,
958 width: '100%',
959 ajax: {
960 url: ajaxurl,
961 dataType: 'json',
962 delay: 250,
963 data: function (params) {
964 return {
965 action: 'wpdm_stats_get_users',
966 term: params.term,
967 __spnonce: '<?php echo wp_create_nonce(WPDM_PUB_NONCE); ?>'
968 };
969 },
970 processResults: function (data) {
971 if (data.success === false) {
972 return { results: [] };
973 }
974 return data;
975 },
976 cache: true
977 },
978 minimumInputLength: 2,
979 escapeMarkup: function (markup) { return markup; }
980 });
981 }
982 }
983
984 // Initialize Select2 when DOM is ready
985 initializeSelect2();
986
987 // Re-initialize if content changes (for dynamic loading)
988 setTimeout(initializeSelect2, 1000);
989
990 // Quick date filter functionality
991 $('.quick-filter-btn').click(function(e) {
992 e.preventDefault();
993 var days = $(this).data('days');
994 var today = new Date();
995 var fromDate = new Date();
996
997 if (days === 1) {
998 // Today only
999 fromDate = new Date(today);
1000 } else {
1001 // Calculate past date
1002 fromDate.setDate(today.getDate() - days + 1);
1003 }
1004
1005 // Format dates for input fields
1006 var formatDate = function(date) {
1007 var year = date.getFullYear();
1008 var month = String(date.getMonth() + 1).padStart(2, '0');
1009 var day = String(date.getDate()).padStart(2, '0');
1010 return year + '-' + month + '-' + day;
1011 };
1012
1013 // Set the values directly to input fields
1014 $('input[name="from_date"]').val(formatDate(fromDate));
1015 $('input[name="to_date"]').val(formatDate(today));
1016
1017 // Visual feedback
1018 $('.quick-filter-btn').removeClass('active');
1019 $(this).addClass('active');
1020
1021 // Auto-submit form after date selection
1022 setTimeout(function() {
1023 $('.elegant-filter-form').submit();
1024 }, 100);
1025 });
1026
1027 // Table sorting functionality
1028 $('.sortable').click(function() {
1029 var $this = $(this);
1030 var sortColumn = $this.data('sort');
1031
1032 // Remove active sort from other columns
1033 $('.sortable').removeClass('sort-asc sort-desc');
1034 $('.sort-icon').removeClass('fa-sort-up fa-sort-down').addClass('fa-sort');
1035
1036 // Determine current sort direction
1037 var currentSort = $this.hasClass('sort-asc') ? 'desc' : 'asc';
1038
1039 // Apply new sort
1040 $this.addClass('sort-' + currentSort);
1041 var sortIcon = currentSort === 'asc' ? 'fa-sort-up' : 'fa-sort-down';
1042 $this.find('.sort-icon').removeClass('fa-sort').addClass(sortIcon);
1043
1044 // Here you would implement actual sorting logic
1045 // For demonstration, we'll just show visual feedback
1046 showSortingFeedback(sortColumn, currentSort);
1047 });
1048
1049 // View toggle functionality
1050 $('.view-btn').click(function() {
1051 var view = $(this).data('view');
1052 $('.view-btn').removeClass('active');
1053 $(this).addClass('active');
1054
1055 if (view === 'cards') {
1056 // Switch to card view
1057 $('.elegant-table-container').addClass('card-view');
1058 showNotification('<?php _e("Switched to card view", "download-manager"); ?>', 'info');
1059 } else {
1060 // Switch to table view
1061 $('.elegant-table-container').removeClass('card-view');
1062 showNotification('<?php _e("Switched to table view", "download-manager"); ?>', 'info');
1063 }
1064 });
1065
1066 // Row hover effects
1067 $(document).on('mouseenter', '.elegant-row', function() {
1068 $(this).addClass('row-hovered');
1069 }).on('mouseleave', '.elegant-row', function() {
1070 $(this).removeClass('row-hovered');
1071 });
1072
1073 // Card hover effects
1074 $(document).on('mouseenter', '.download-card', function() {
1075 $(this).addClass('card-hovered');
1076 }).on('mouseleave', '.download-card', function() {
1077 $(this).removeClass('card-hovered');
1078 });
1079
1080 // Helper functions
1081 function showSortingFeedback(column, direction) {
1082 var message = '<?php _e("Sorting by", "download-manager"); ?> ' + column + ' (' + direction + ')';
1083 showNotification(message, 'info');
1084 }
1085
1086 function showNotification(message, type) {
1087 var notification = $('<div class="elegant-notification ' + type + '">')
1088 .html('<i class="fas fa-info-circle"></i> ' + message);
1089
1090 $('body').append(notification);
1091
1092 notification.fadeIn(300).delay(3000).fadeOut(300, function() {
1093 $(this).remove();
1094 });
1095 }
1096
1097 // Initialize tooltips for better UX
1098 $('[title]').each(function() {
1099 $(this).tooltip({
1100 placement: 'top',
1101 trigger: 'hover'
1102 });
1103 });
1104
1105 // Handle form submission
1106 $('.elegant-filter-form').on('submit', function(e) {
1107 // Force Select2 to update the underlying select elements
1108 $('#cats, #package_ids, #user_ids').each(function() {
1109 $(this).trigger('change.select2');
1110 });
1111
1112 return true; // Allow form submission
1113 });
1114
1115 // Add loading states to buttons
1116 $('.elegant-filter-form button[type="submit"]').click(function(e) {
1117 var $btn = $(this);
1118 var originalText = $btn.html();
1119
1120 $btn.html('<i class="fas fa-spinner fa-spin"></i> <?php _e("Loading...", "download-manager"); ?>');
1121
1122 // Allow natural form submission
1123 setTimeout(function() {
1124 // This will only run if the page doesn't redirect
1125 $btn.html(originalText);
1126 }, 1000);
1127 });
1128 });
1129 </script>
1130