Initial commit: Aurora Admin plugin
This commit is contained in:
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
namespace AuroraAdmin\Rest;
|
||||
|
||||
use AuroraAdmin\Analytics\Analytics;
|
||||
|
||||
defined("ABSPATH") || exit();
|
||||
|
||||
/**
|
||||
* Aggregates the first-party pageview table into the figures the Analytics
|
||||
* tab cards need. GET aurora-admin/v1/analytics?from=YYYY-MM-DD&to=YYYY-MM-DD
|
||||
*/
|
||||
class AnalyticsData
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
add_action("rest_api_init", [self::class, "register"]);
|
||||
}
|
||||
|
||||
public static function register()
|
||||
{
|
||||
register_rest_route("aurora-admin/v1", "/analytics", [
|
||||
"methods" => "GET",
|
||||
"callback" => [self::class, "handle"],
|
||||
"permission_callback" => function () {
|
||||
return current_user_can("read");
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
public static function handle($request)
|
||||
{
|
||||
global $wpdb;
|
||||
$table = Analytics::table_name();
|
||||
|
||||
$from = $request->get_param("from") ?: gmdate("Y-m-d", strtotime("-7 days"));
|
||||
$to = $request->get_param("to") ?: gmdate("Y-m-d");
|
||||
$from_dt = $from . " 00:00:00";
|
||||
$to_dt = $to . " 23:59:59";
|
||||
|
||||
$where = $wpdb->prepare("created_at BETWEEN %s AND %s", $from_dt, $to_dt);
|
||||
|
||||
// --- Totals ---
|
||||
$total = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$table} WHERE {$where}");
|
||||
$unique = (int) $wpdb->get_var("SELECT COUNT(DISTINCT session_id) FROM {$table} WHERE {$where}");
|
||||
|
||||
// --- Daily series ---
|
||||
$rows = $wpdb->get_results(
|
||||
"SELECT DATE(created_at) AS d, COUNT(*) AS c FROM {$table} WHERE {$where} GROUP BY DATE(created_at)",
|
||||
OBJECT_K
|
||||
);
|
||||
$series = [];
|
||||
$cursor = strtotime($from);
|
||||
$end = strtotime($to);
|
||||
while ($cursor <= $end) {
|
||||
$key = gmdate("Y-m-d", $cursor);
|
||||
$series[] = [
|
||||
"date" => gmdate("M j", $cursor),
|
||||
"count" => isset($rows[$key]) ? (int) $rows[$key]->c : 0,
|
||||
];
|
||||
$cursor = strtotime("+1 day", $cursor);
|
||||
}
|
||||
|
||||
// --- Device breakdown ---
|
||||
$devices = $wpdb->get_results(
|
||||
"SELECT device, COUNT(*) AS c FROM {$table} WHERE {$where} GROUP BY device ORDER BY c DESC"
|
||||
);
|
||||
$deviceUsage = [];
|
||||
foreach ($devices as $d) {
|
||||
$deviceUsage[] = [
|
||||
"device" => $d->device,
|
||||
"count" => (int) $d->c,
|
||||
"pct" => $total ? round(($d->c / $total) * 100) : 0,
|
||||
];
|
||||
}
|
||||
|
||||
// --- Top pages / referrers / countries ---
|
||||
$topPages = self::top($wpdb, $table, $where, "url", 6);
|
||||
$topReferrers = self::top($wpdb, $table, $where, "referrer", 6, true);
|
||||
$topCountries = self::top($wpdb, $table, $where, "country", 6, true);
|
||||
|
||||
// --- Bounce rate & avg session depth ---
|
||||
$sessionCounts = $wpdb->get_results(
|
||||
"SELECT session_id, COUNT(*) AS c,
|
||||
TIMESTAMPDIFF(SECOND, MIN(created_at), MAX(created_at)) AS dur
|
||||
FROM {$table} WHERE {$where} GROUP BY session_id"
|
||||
);
|
||||
$sessions = count($sessionCounts);
|
||||
$bounces = 0;
|
||||
$totalDur = 0;
|
||||
foreach ($sessionCounts as $s) {
|
||||
if ((int) $s->c <= 1) {
|
||||
$bounces++;
|
||||
}
|
||||
$totalDur += (int) $s->dur;
|
||||
}
|
||||
$bounceRate = $sessions ? round(($bounces / $sessions) * 100) : 0;
|
||||
$avgSeconds = $sessions ? round($totalDur / $sessions) : 0;
|
||||
|
||||
// --- Active users (last 5 minutes) ---
|
||||
$activeCutoff = gmdate("Y-m-d H:i:s", current_time("timestamp") - 300);
|
||||
$active = (int) $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT COUNT(DISTINCT session_id) FROM {$table} WHERE created_at >= %s",
|
||||
$activeCutoff
|
||||
)
|
||||
);
|
||||
|
||||
return new \WP_REST_Response([
|
||||
"pageViews" => ["total" => $total, "unique" => $unique, "series" => $series],
|
||||
"deviceUsage" => $deviceUsage,
|
||||
"topPages" => $topPages,
|
||||
"topReferrers" => $topReferrers,
|
||||
"topCountries" => $topCountries,
|
||||
"bounce" => [
|
||||
"rate" => $bounceRate,
|
||||
"avgTime" => self::format_duration($avgSeconds),
|
||||
"sessions" => $sessions,
|
||||
],
|
||||
"activeUsers" => $active,
|
||||
], 200);
|
||||
}
|
||||
|
||||
private static function top($wpdb, $table, $where, $col, $limit, $skip_empty = false)
|
||||
{
|
||||
$extra = $skip_empty ? " AND {$col} <> ''" : "";
|
||||
$results = $wpdb->get_results(
|
||||
"SELECT {$col} AS label, COUNT(*) AS c FROM {$table}
|
||||
WHERE {$where}{$extra} GROUP BY {$col} ORDER BY c DESC LIMIT {$limit}"
|
||||
);
|
||||
$out = [];
|
||||
foreach ($results as $r) {
|
||||
$out[] = ["label" => $r->label !== "" ? $r->label : "Direct", "count" => (int) $r->c];
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
private static function format_duration($seconds)
|
||||
{
|
||||
if ($seconds <= 0) {
|
||||
return "0s";
|
||||
}
|
||||
$m = floor($seconds / 60);
|
||||
$s = $seconds % 60;
|
||||
return $m > 0 ? "{$m}m {$s}s" : "{$s}s";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,292 @@
|
||||
<?php
|
||||
namespace AuroraAdmin\Rest;
|
||||
|
||||
defined("ABSPATH") || exit();
|
||||
|
||||
/**
|
||||
* REST endpoint that supplies all data for the Aurora dashboard cards.
|
||||
* GET aurora-admin/v1/dashboard?from=YYYY-MM-DD&to=YYYY-MM-DD
|
||||
*
|
||||
* Everything is computed fresh here from core WordPress APIs — no reuse of
|
||||
* the original plugin's data layer.
|
||||
*/
|
||||
class DashboardData
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
add_action("rest_api_init", [self::class, "register"]);
|
||||
}
|
||||
|
||||
public static function register()
|
||||
{
|
||||
register_rest_route("aurora-admin/v1", "/dashboard", [
|
||||
"methods" => "GET",
|
||||
"callback" => [self::class, "handle"],
|
||||
"permission_callback" => function () {
|
||||
return current_user_can("read");
|
||||
},
|
||||
"args" => [
|
||||
"from" => ["type" => "string", "required" => false],
|
||||
"to" => ["type" => "string", "required" => false],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public static function handle($request)
|
||||
{
|
||||
$to = self::parse_date($request->get_param("to"), "now");
|
||||
$from = self::parse_date($request->get_param("from"), "-7 days");
|
||||
|
||||
return new \WP_REST_Response([
|
||||
"recentContent" => self::recent_content(),
|
||||
"scheduledContent" => self::scheduled_content(),
|
||||
"recentComments" => self::recent_comments(),
|
||||
"userAnalytics" => self::user_analytics($from, $to),
|
||||
"mediaAnalytics" => self::media_analytics(),
|
||||
"serverHealth" => self::server_health(),
|
||||
], 200);
|
||||
}
|
||||
|
||||
private static function parse_date($value, $fallback)
|
||||
{
|
||||
$ts = $value ? strtotime($value) : false;
|
||||
if ($ts === false) {
|
||||
$ts = strtotime($fallback);
|
||||
}
|
||||
return $ts;
|
||||
}
|
||||
|
||||
private static function recent_content()
|
||||
{
|
||||
$counts = wp_count_posts("post");
|
||||
$recent = get_posts([
|
||||
"post_type" => "post",
|
||||
"post_status" => ["publish", "draft", "future"],
|
||||
"numberposts" => 5,
|
||||
"orderby" => "date",
|
||||
"order" => "DESC",
|
||||
]);
|
||||
|
||||
$latest = [];
|
||||
foreach ($recent as $p) {
|
||||
$latest[] = [
|
||||
"id" => $p->ID,
|
||||
"title" => get_the_title($p),
|
||||
"author" => get_the_author_meta("display_name", $p->post_author),
|
||||
"date" => get_the_date("M j, Y", $p),
|
||||
"status" => $p->post_status,
|
||||
"editUrl" => get_edit_post_link($p->ID, "raw"),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
"published" => (int) $counts->publish,
|
||||
"draft" => (int) $counts->draft,
|
||||
"scheduled" => (int) $counts->future,
|
||||
"total" => (int) $counts->publish + (int) $counts->draft + (int) $counts->future,
|
||||
"latest" => $latest,
|
||||
"listUrl" => admin_url("edit.php"),
|
||||
];
|
||||
}
|
||||
|
||||
private static function scheduled_content()
|
||||
{
|
||||
$future = get_posts([
|
||||
"post_type" => "post",
|
||||
"post_status" => "future",
|
||||
"numberposts" => 100,
|
||||
"orderby" => "date",
|
||||
"order" => "ASC",
|
||||
]);
|
||||
|
||||
$days = [];
|
||||
foreach ($future as $p) {
|
||||
$days[get_the_date("Y-m-d", $p)][] = [
|
||||
"id" => $p->ID,
|
||||
"title" => get_the_title($p),
|
||||
"time" => get_the_time("g:i a", $p),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
"count" => count($future),
|
||||
"days" => $days,
|
||||
];
|
||||
}
|
||||
|
||||
private static function recent_comments()
|
||||
{
|
||||
$counts = wp_count_comments();
|
||||
$recent = get_comments([
|
||||
"number" => 5,
|
||||
"status" => "approve",
|
||||
"orderby" => "comment_date_gmt",
|
||||
"order" => "DESC",
|
||||
]);
|
||||
|
||||
$latest = [];
|
||||
foreach ($recent as $c) {
|
||||
$latest[] = [
|
||||
"id" => $c->comment_ID,
|
||||
"author" => $c->comment_author,
|
||||
"excerpt" => wp_trim_words($c->comment_content, 18),
|
||||
"postTitle" => get_the_title($c->comment_post_ID),
|
||||
"date" => mysql2date("M j, Y", $c->comment_date),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
"approved" => (int) $counts->approved,
|
||||
"pending" => (int) $counts->moderated,
|
||||
"spam" => (int) $counts->spam,
|
||||
"total" => (int) $counts->approved,
|
||||
"latest" => $latest,
|
||||
"listUrl" => admin_url("edit-comments.php"),
|
||||
];
|
||||
}
|
||||
|
||||
private static function user_analytics($from, $to)
|
||||
{
|
||||
$total = count_users();
|
||||
$total_users = (int) $total["total_users"];
|
||||
|
||||
$from_date = gmdate("Y-m-d 00:00:00", $from);
|
||||
$to_date = gmdate("Y-m-d 23:59:59", $to);
|
||||
|
||||
$in_range = get_users([
|
||||
"date_query" => [["after" => $from_date, "before" => $to_date, "inclusive" => true]],
|
||||
"fields" => "ID",
|
||||
]);
|
||||
|
||||
$recent7 = get_users([
|
||||
"date_query" => [["after" => "-7 days", "inclusive" => true]],
|
||||
"fields" => "ID",
|
||||
]);
|
||||
|
||||
// Daily registration series across the range
|
||||
$series = [];
|
||||
$cursor = strtotime(gmdate("Y-m-d", $from));
|
||||
$end = strtotime(gmdate("Y-m-d", $to));
|
||||
$by_day = [];
|
||||
foreach ($in_range as $uid) {
|
||||
$reg = get_userdata($uid)->user_registered;
|
||||
$by_day[mysql2date("Y-m-d", $reg)] = (isset($by_day[mysql2date("Y-m-d", $reg)]) ? $by_day[mysql2date("Y-m-d", $reg)] : 0) + 1;
|
||||
}
|
||||
while ($cursor <= $end) {
|
||||
$key = gmdate("Y-m-d", $cursor);
|
||||
$series[] = [
|
||||
"date" => gmdate("M j", $cursor),
|
||||
"count" => isset($by_day[$key]) ? $by_day[$key] : 0,
|
||||
];
|
||||
$cursor = strtotime("+1 day", $cursor);
|
||||
}
|
||||
|
||||
return [
|
||||
"total" => $total_users,
|
||||
"inRange" => count($in_range),
|
||||
"recent7d" => count($recent7),
|
||||
"series" => $series,
|
||||
];
|
||||
}
|
||||
|
||||
private static function media_analytics()
|
||||
{
|
||||
$attachments = get_posts([
|
||||
"post_type" => "attachment",
|
||||
"post_status" => "inherit",
|
||||
"numberposts" => -1,
|
||||
"fields" => "ids",
|
||||
]);
|
||||
|
||||
$total_size = 0;
|
||||
$large = 0;
|
||||
$recent30 = 0;
|
||||
$thirty_ago = strtotime("-30 days");
|
||||
|
||||
foreach ($attachments as $id) {
|
||||
$file = get_attached_file($id);
|
||||
$size = $file && file_exists($file) ? filesize($file) : 0;
|
||||
$total_size += $size;
|
||||
if ($size > 5 * 1024 * 1024) {
|
||||
$large++;
|
||||
}
|
||||
if (strtotime(get_post_field("post_date", $id)) >= $thirty_ago) {
|
||||
$recent30++;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
"totalFiles" => count($attachments),
|
||||
"totalSize" => self::format_bytes($total_size),
|
||||
"recent30d" => $recent30,
|
||||
"unused" => 0,
|
||||
"largeFiles" => $large,
|
||||
"diskUsage" => self::format_bytes($total_size),
|
||||
"manageUrl" => admin_url("upload.php"),
|
||||
];
|
||||
}
|
||||
|
||||
private static function server_health()
|
||||
{
|
||||
$mem_limit = self::to_bytes(ini_get("memory_limit"));
|
||||
$mem_used = memory_get_usage(true);
|
||||
$disk_total = @disk_total_space(ABSPATH) ?: 0;
|
||||
$disk_free = @disk_free_space(ABSPATH) ?: 0;
|
||||
$disk_used = $disk_total - $disk_free;
|
||||
|
||||
$updates = function_exists("wp_get_update_data") ? wp_get_update_data() : ["counts" => []];
|
||||
$counts = isset($updates["counts"]) ? $updates["counts"] : [];
|
||||
|
||||
return [
|
||||
"wp" => get_bloginfo("version"),
|
||||
"php" => PHP_VERSION,
|
||||
"memory" => [
|
||||
"used" => self::format_bytes($mem_used),
|
||||
"total" => self::format_bytes($mem_limit),
|
||||
"pct" => $mem_limit > 0 ? round(($mem_used / $mem_limit) * 100, 1) : 0,
|
||||
],
|
||||
"disk" => [
|
||||
"used" => self::format_bytes($disk_used),
|
||||
"total" => self::format_bytes($disk_total),
|
||||
"pct" => $disk_total > 0 ? round(($disk_used / $disk_total) * 100, 1) : 0,
|
||||
],
|
||||
"plugins" => isset($counts["plugins"]) ? (int) $counts["plugins"] : 0,
|
||||
"themes" => isset($counts["themes"]) ? (int) $counts["themes"] : 0,
|
||||
"core" => isset($counts["wordpress"]) ? (int) $counts["wordpress"] : 0,
|
||||
"updates" => isset($counts["total"]) ? (int) $counts["total"] : 0,
|
||||
"ssl" => is_ssl(),
|
||||
"timezone" => wp_timezone_string(),
|
||||
];
|
||||
}
|
||||
|
||||
private static function format_bytes($bytes)
|
||||
{
|
||||
$bytes = (float) $bytes;
|
||||
$units = ["B", "KB", "MB", "GB", "TB"];
|
||||
$i = 0;
|
||||
while ($bytes >= 1024 && $i < count($units) - 1) {
|
||||
$bytes /= 1024;
|
||||
$i++;
|
||||
}
|
||||
return round($bytes, 2) . " " . $units[$i];
|
||||
}
|
||||
|
||||
private static function to_bytes($val)
|
||||
{
|
||||
$val = trim($val);
|
||||
if ($val === "" || $val === "-1") {
|
||||
return 0;
|
||||
}
|
||||
$last = strtolower($val[strlen($val) - 1]);
|
||||
$num = (float) $val;
|
||||
switch ($last) {
|
||||
case "g":
|
||||
$num *= 1024;
|
||||
case "m":
|
||||
$num *= 1024;
|
||||
case "k":
|
||||
$num *= 1024;
|
||||
}
|
||||
return $num;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user