0) { /* translators: 1: minutes, 2: seconds */ return sprintf(__('%1$d min %2$d s', 'statinform'), $m, $s); } /* translators: %d: number of minutes */ return sprintf(__('%d min', 'statinform'), $m); } $h = floor($m / 60); $m = $m % 60; /* translators: 1: hours, 2: minutes */ return sprintf(__('%1$d h %2$d min', 'statinform'), $h, $m); } } /** * Render the analytics dashboard page. */ function eistat_render_dashboard() { global $wpdb; $eistat_p = $wpdb->prefix . 'eistat_'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended $view = isset($_GET['view']) ? sanitize_key(wp_unslash($_GET['view'])) : 'main'; // --- Aggregation result notice --- // phpcs:ignore WordPress.Security.NonceVerification.Recommended if (isset($_GET['eistat_updated']) && $_GET['eistat_updated'] === '1') { $ms = get_option('eistat_last_manual_run_ms'); $result = get_option('eistat_last_manual_run_result', -2); if ($result === -2) { echo '

' . esc_html__('Could not retrieve the aggregation result.', 'statinform') . '

'; } elseif ($result === -1) { echo '

' . esc_html__('The aggregator is busy with another process.', 'statinform') . ' ' . esc_html__('Please try again in a few seconds.', 'statinform') . '

'; } elseif ($result === 0) { echo '

' . esc_html__('The aggregator ran but found no events to process.', 'statinform') . '

'; } else { echo '

' . esc_html__('Aggregation completed.', 'statinform') . ' '; if ($ms !== false) { /* translators: %d: milliseconds */ echo esc_html(sprintf(__('Time: %d ms.', 'statinform'), (int) $ms)) . ' '; } /* translators: %d: number of processed events */ echo esc_html(sprintf(__('Events processed: %d.', 'statinform'), (int) $result)); echo '

'; } } eistat_render_freshness(); eistat_render_size_notice($eistat_p); // --- Router --- switch ($view) { case 'day': require EISTAT_DIR . 'admin/views/day.php'; break; case 'session': require EISTAT_DIR . 'admin/views/session.php'; break; case 'source': require EISTAT_DIR . 'admin/views/source.php'; break; case 'paths': require EISTAT_DIR . 'admin/views/paths.php'; break; case 'entry-exit': require EISTAT_DIR . 'admin/views/entry-exit.php'; break; case 'links': require EISTAT_DIR . 'admin/views/links.php'; break; case 'archives': require EISTAT_DIR . 'admin/views/archives.php'; break; default: require EISTAT_DIR . 'admin/views/main.php'; break; } } eistat_render_dashboard(); /** * Render a "data freshness" indicator. */ function eistat_render_freshness() { $last = get_option('eistat_last_aggregation_ts', 0); if (!$last) { echo '
' . '' . esc_html__('Aggregation has never run yet', 'statinform') . '
'; return; } $age = time() - $last; if ($age < 60) { $age_str = sprintf( /* translators: %d: number of seconds */ _n('%d second ago', '%d seconds ago', $age, 'statinform'), $age ); } elseif ($age < 3600) { $mins = (int) round($age / 60); $age_str = sprintf( /* translators: %d: number of minutes */ _n('%d minute ago', '%d minutes ago', $mins, 'statinform'), $mins ); } else { $hours = (int) round($age / 3600); $age_str = sprintf( /* translators: %d: number of hours */ _n('%d hour ago', '%d hours ago', $hours, 'statinform'), $hours ); } $class = 'eistat-fresh'; $note = ''; if ($age > 3600) { $class .= ' stale'; $note = ' (' . __('cron may not be running', 'statinform') . ')'; } if ($age > 6 * 3600) { $class = 'eistat-fresh stale-hard'; $note = ' (' . __('please check cron', 'statinform') . ')'; } echo '
' . '' /* translators: %s: human-readable time since last aggregation */ . esc_html(sprintf(__('Data updated %s', 'statinform'), $age_str)) . esc_html($note) . '
'; } /** * Show a notice when the analytics DB grows large. * * @param string $p Table prefix for analytics tables. */ function eistat_render_size_notice($p) { $info = get_transient('eistat_db_size_info'); if ($info === false) { global $wpdb; $tables = ['sessions', 'pageviews', 'sources', 'daily', 'transitions', 'events']; $total_mb = 0; $total_rows = 0; foreach ($tables as $t) { $full = $p . $t; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter $row = $wpdb->get_row($wpdb->prepare("SHOW TABLE STATUS LIKE %s", $full)); if ($row) { $total_mb += ($row->Data_length + $row->Index_length) / 1024 / 1024; $total_rows += (int) $row->Rows; } } $info = ['mb' => round($total_mb, 1), 'rows' => $total_rows]; set_transient('eistat_db_size_info', $info, HOUR_IN_SECONDS); } if ($info['rows'] > 100000 || $info['mb'] > 200) { $url = admin_url('admin.php?page=eistat-dashboard&view=archives'); echo '
' . 'ℹ️ ' . sprintf( /* translators: 1: number of rows, 2: size in MB */ esc_html__('The analytics database has grown to %1$s records (about %2$s MB). Consider archiving and deleting data older than a year.', 'statinform'), '' . esc_html(number_format_i18n($info['rows'])) . '', '' . esc_html($info['mb']) . '' ) . ' ' . esc_html__('Go to management →', 'statinform') . '
'; } }