wp-multisite-dashboard/includes/class-network-data.php
2025-06-24 23:35:55 +08:00

1019 lines
31 KiB
PHP
Executable file

<?php
if (!defined("ABSPATH")) {
exit();
}
class WP_MSD_Network_Data
{
private $wpdb;
private $cache_group = "msd_network_data";
private $cache_timeout = 3600;
public function __construct()
{
global $wpdb;
$this->wpdb = $wpdb;
}
public function get_total_sites()
{
$cache_key = "total_sites";
$cached = $this->get_cache($cache_key);
if ($cached !== false) {
return $cached;
}
$count = get_sites(["count" => true]);
$this->set_cache($cache_key, $count, 1800);
return $count;
}
public function get_total_users()
{
$cache_key = "total_users";
$cached = $this->get_cache($cache_key);
if ($cached !== false) {
return $cached;
}
$user_count = $this->wpdb->get_var(
"SELECT COUNT(DISTINCT ID) FROM {$this->wpdb->users}"
);
$count = intval($user_count);
$this->set_cache($cache_key, $count, 1800);
return $count;
}
public function get_total_posts()
{
$cache_key = "total_posts";
$cached = $this->get_cache($cache_key);
if ($cached !== false) {
return $cached;
}
$sites = get_sites(["number" => 100]);
$total_posts = 0;
foreach ($sites as $site) {
$blog_id = $site->blog_id;
switch_to_blog($blog_id);
$posts_count = wp_count_posts();
$total_posts += $posts_count->publish;
restore_current_blog();
}
$this->set_cache($cache_key, $total_posts, 3600);
return $total_posts;
}
public function get_total_pages()
{
$cache_key = "total_pages";
$cached = $this->get_cache($cache_key);
if ($cached !== false) {
return $cached;
}
$sites = get_sites(["number" => 100]);
$total_pages = 0;
foreach ($sites as $site) {
$blog_id = $site->blog_id;
switch_to_blog($blog_id);
$pages_count = wp_count_posts("page");
$total_pages += $pages_count->publish;
restore_current_blog();
}
$this->set_cache($cache_key, $total_pages, 3600);
return $total_pages;
}
public function get_multisite_configuration()
{
$cache_key = "multisite_configuration";
$cached = $this->get_cache($cache_key);
if ($cached !== false) {
return $cached;
}
$config = [
"installation_type" => is_subdomain_install()
? "subdomain"
: "subdirectory",
"installation_type_label" => is_subdomain_install()
? __("Subdomain Installation", "wp-multisite-dashboard")
: __("Subdirectory Installation", "wp-multisite-dashboard"),
"domain_current_site" => DOMAIN_CURRENT_SITE,
"path_current_site" => PATH_CURRENT_SITE,
"site_id_current_site" => SITE_ID_CURRENT_SITE,
"blog_id_current_site" => BLOG_ID_CURRENT_SITE,
"multisite_enabled" => true,
"cookie_domain" => defined("COOKIE_DOMAIN") ? COOKIE_DOMAIN : "",
];
$this->set_cache($cache_key, $config, 7200);
return $config;
}
public function get_network_information()
{
$cache_key = "network_information";
$cached = $this->get_cache($cache_key);
if ($cached !== false) {
return $cached;
}
global $wp_version;
$info = [
"network_name" => get_network_option(null, "site_name"),
"network_admin_email" => get_network_option(null, "admin_email"),
"registration" => get_network_option(null, "registration"),
"registration_label" => $this->get_registration_label(
get_network_option(null, "registration")
),
"blog_upload_space" => get_space_allowed(),
"blog_upload_space_formatted" => get_space_allowed() . " MB",
"fileupload_maxk" => get_network_option(null, "fileupload_maxk"),
"fileupload_maxk_formatted" => size_format(
get_network_option(null, "fileupload_maxk") * 1024
),
"illegal_names" => get_network_option(null, "illegal_names"),
"limited_email_domains" => get_network_option(
null,
"limited_email_domains"
),
"banned_email_domains" => get_network_option(
null,
"banned_email_domains"
),
"welcome_email" => get_network_option(null, "welcome_email"),
"first_post" => get_network_option(null, "first_post"),
"first_page" => get_network_option(null, "first_page"),
"first_comment" => get_network_option(null, "first_comment"),
"first_comment_url" => get_network_option(
null,
"first_comment_url"
),
"first_comment_author" => get_network_option(
null,
"first_comment_author"
),
"welcome_user_email" => get_network_option(
null,
"welcome_user_email"
),
"default_language" => get_network_option(null, "WPLANG") ?: "en_US",
"wp_version" => $wp_version,
"active_sitewide_plugins_count" => count(
get_site_option("active_sitewide_plugins", [])
),
"allowed_themes_count" => count(
get_site_option("allowedthemes", [])
),
];
$this->set_cache($cache_key, $info, 3600);
return $info;
}
private function get_registration_label($registration)
{
$labels = [
"none" => __("Registration disabled", "wp-multisite-dashboard"),
"user" => __("User registration only", "wp-multisite-dashboard"),
"blog" => __("Site registration only", "wp-multisite-dashboard"),
"all" => __("User and site registration", "wp-multisite-dashboard"),
];
return $labels[$registration] ??
__("Unknown", "wp-multisite-dashboard");
}
public function get_recent_network_activity($limit = 10)
{
$cache_key = "recent_network_activity_{$limit}";
$cached = $this->get_cache($cache_key);
if ($cached !== false) {
return $cached;
}
$sites = get_sites([
"number" => 20,
"orderby" => "last_updated",
"order" => "DESC",
]);
$activities = [];
foreach ($sites as $site) {
$blog_id = $site->blog_id;
switch_to_blog($blog_id);
$recent_posts = get_posts([
"numberposts" => 3,
"post_status" => "publish",
"orderby" => "date",
"order" => "DESC",
]);
$recent_pages = get_posts([
"numberposts" => 2,
"post_type" => "page",
"post_status" => "publish",
"orderby" => "date",
"order" => "DESC",
]);
$site_name = get_bloginfo("name");
$site_url = get_site_url();
$admin_url = get_admin_url();
foreach ($recent_posts as $post) {
$activities[] = [
"type" => "post",
"type_label" => __("Post", "wp-multisite-dashboard"),
"title" => $post->post_title,
"content" => wp_trim_words($post->post_content, 15),
"author" => get_the_author_meta(
"display_name",
$post->post_author
),
"date" => $post->post_date,
"date_human" =>
human_time_diff(strtotime($post->post_date)) .
" " .
__("ago", "wp-multisite-dashboard"),
"site_name" => $site_name,
"site_url" => $site_url,
"edit_url" =>
$admin_url .
"post.php?post=" .
$post->ID .
"&action=edit",
"view_url" => get_permalink($post->ID),
"blog_id" => $blog_id,
"timestamp" => strtotime($post->post_date),
];
}
foreach ($recent_pages as $page) {
$activities[] = [
"type" => "page",
"type_label" => __("Page", "wp-multisite-dashboard"),
"title" => $page->post_title,
"content" => wp_trim_words($page->post_content, 15),
"author" => get_the_author_meta(
"display_name",
$page->post_author
),
"date" => $page->post_date,
"date_human" =>
human_time_diff(strtotime($page->post_date)) .
" " .
__("ago", "wp-multisite-dashboard"),
"site_name" => $site_name,
"site_url" => $site_url,
"edit_url" =>
$admin_url .
"post.php?post=" .
$page->ID .
"&action=edit",
"view_url" => get_permalink($page->ID),
"blog_id" => $blog_id,
"timestamp" => strtotime($page->post_date),
];
}
restore_current_blog();
}
usort($activities, function ($a, $b) {
return $b["timestamp"] - $a["timestamp"];
});
$activities = array_slice($activities, 0, $limit);
$this->set_cache($cache_key, $activities, 1800);
return $activities;
}
public function get_total_storage_used()
{
$cache_key = "total_storage";
$cached = $this->get_cache($cache_key);
if ($cached !== false) {
return $cached;
}
$storage_data = $this->get_storage_usage_data();
$total_bytes = 0;
foreach ($storage_data["sites"] as $site) {
$total_bytes += $site["storage_bytes"];
}
$formatted_storage = size_format($total_bytes);
$this->set_cache($cache_key, $formatted_storage, 3600);
return $formatted_storage;
}
public function get_storage_usage_data($limit = 10)
{
$cache_key = "storage_usage_data_{$limit}";
$cached = $this->get_cache($cache_key);
if ($cached !== false) {
return $cached;
}
$sites = get_sites(["number" => 100]);
$storage_data = [
"total_bytes" => 0,
"total_formatted" => "0 B",
"sites" => [],
"summary" => [
"sites_analyzed" => 0,
"average_per_site" => 0,
"largest_site" => null,
"storage_limit" => get_space_allowed(),
],
];
$site_storage = [];
$total_bytes = 0;
foreach ($sites as $site) {
$blog_id = $site->blog_id;
$storage_bytes = $this->get_site_storage_usage($blog_id);
if ($storage_bytes > 0) {
$storage_limit_mb = get_space_allowed();
$usage_percentage =
$storage_limit_mb > 0
? ($storage_bytes / (1024 * 1024) / $storage_limit_mb) *
100
: 0;
$site_info = [
"blog_id" => $blog_id,
"name" => $this->get_site_name($blog_id),
"domain" => $site->domain . $site->path,
"storage_bytes" => $storage_bytes,
"storage_formatted" => size_format($storage_bytes),
"storage_limit_mb" => $storage_limit_mb,
"usage_percentage" => round($usage_percentage, 1),
"status" => $this->get_storage_status_from_percentage(
$usage_percentage
),
"admin_url" => get_admin_url($blog_id),
];
$site_storage[] = $site_info;
$total_bytes += $storage_bytes;
}
}
usort($site_storage, function ($a, $b) {
return $b["storage_bytes"] <=> $a["storage_bytes"];
});
$storage_data["sites"] = array_slice($site_storage, 0, $limit);
$storage_data["total_bytes"] = $total_bytes;
$storage_data["total_formatted"] = size_format($total_bytes);
$storage_data["summary"]["sites_analyzed"] = count($site_storage);
$storage_data["summary"]["average_per_site"] =
count($site_storage) > 0 ? $total_bytes / count($site_storage) : 0;
$storage_data["summary"]["largest_site"] = !empty($site_storage)
? $site_storage[0]
: null;
$this->set_cache($cache_key, $storage_data, 3600);
return $storage_data;
}
private function get_storage_status_from_percentage($percentage)
{
if ($percentage > 90) {
return "critical";
} elseif ($percentage > 75) {
return "warning";
}
return "good";
}
public function get_overall_network_status()
{
$cache_key = "network_status";
$cached = $this->get_cache($cache_key);
if ($cached !== false) {
return $cached;
}
$total_sites = $this->get_total_sites();
$status_data = [
"status" => "healthy",
"message" => __(
"All systems operating normally",
"wp-multisite-dashboard"
),
];
if ($total_sites == 0) {
$status_data = [
"status" => "warning",
"message" => __("No sites found", "wp-multisite-dashboard"),
];
}
$this->set_cache($cache_key, $status_data, 900);
return $status_data;
}
public function get_recent_active_sites($limit = 5)
{
$cache_key = "recent_sites_{$limit}";
$cached = $this->get_cache($cache_key);
if ($cached !== false) {
return $cached;
}
$sites = $this->fetch_active_sites($limit * 2);
$site_data = [];
foreach ($sites as $site) {
$blog_id = $site->blog_id;
$site_info = [
"blog_id" => $blog_id,
"name" => $this->get_site_name($blog_id),
"domain" => $site->domain . $site->path,
"users" => $this->get_site_user_count($blog_id),
"last_activity" => $this->get_site_last_activity($blog_id),
"admin_url" => get_admin_url($blog_id),
"view_url" => get_site_url($blog_id),
"status" => $this->get_site_status($blog_id),
"favicon" => $this->get_site_favicon($blog_id),
];
$site_info["last_activity_human"] = $site_info["last_activity"]
? human_time_diff(strtotime($site_info["last_activity"])) .
" " .
__("ago", "wp-multisite-dashboard")
: __("No recent activity", "wp-multisite-dashboard");
$site_data[] = $site_info;
}
usort($site_data, function ($a, $b) {
if (empty($a["last_activity"])) {
return 1;
}
if (empty($b["last_activity"])) {
return -1;
}
return strtotime($b["last_activity"]) -
strtotime($a["last_activity"]);
});
$recent_sites = array_slice($site_data, 0, $limit);
$this->set_cache($cache_key, $recent_sites, 1800);
return $recent_sites;
}
public function get_network_settings_overview()
{
$cache_key = "network_settings_overview";
$cached = $this->get_cache($cache_key);
if ($cached !== false) {
return $cached;
}
global $wp_version;
$settings_data = [
"network_info" => [
"network_name" => get_network_option(null, "site_name"),
"network_admin_email" => get_network_option(
null,
"admin_email"
),
"registration_allowed" => get_network_option(
null,
"registration"
),
"subdomain_install" => is_subdomain_install(),
"max_upload_size" => size_format(wp_max_upload_size()),
"blog_upload_space" => get_space_allowed(),
"file_upload_max_size" => size_format(wp_max_upload_size()),
"max_execution_time" => ini_get("max_execution_time"),
"memory_limit" => ini_get("memory_limit"),
],
"theme_plugin_settings" => [
"network_active_plugins" => count(
get_site_option("active_sitewide_plugins", [])
),
"network_themes" => $this->count_network_themes(),
"plugin_auto_updates" => $this->check_plugin_auto_updates(),
"theme_auto_updates" => $this->check_theme_auto_updates(),
],
"quick_actions" => [
"network_settings_url" => network_admin_url("settings.php"),
"network_sites_url" => network_admin_url("sites.php"),
"network_users_url" => network_admin_url("users.php"),
"network_themes_url" => network_admin_url("themes.php"),
"network_plugins_url" => network_admin_url("plugins.php"),
"network_updates_url" => network_admin_url("update-core.php"),
],
"system_status" => [
"wordpress_version" => $wp_version,
"php_version" => phpversion(),
"mysql_version" => $this->wpdb->db_version(),
"multisite_enabled" => true,
"last_updated" => current_time("mysql"),
],
];
$this->set_cache($cache_key, $settings_data, 1800);
return $settings_data;
}
private function count_network_themes()
{
$themes = wp_get_themes(["allowed" => "network"]);
return count($themes);
}
private function check_plugin_auto_updates()
{
return get_site_option("auto_update_plugins", []);
}
private function check_theme_auto_updates()
{
return get_site_option("auto_update_themes", []);
}
private function get_site_favicon($blog_id)
{
$cache_key = "site_favicon_{$blog_id}";
$cached = wp_cache_get($cache_key, $this->cache_group);
if ($cached !== false) {
return $cached;
}
switch_to_blog($blog_id);
$favicon_url = "";
$site_icon_id = get_option("site_icon");
if ($site_icon_id) {
$favicon_url = wp_get_attachment_image_url($site_icon_id, "full");
}
if (empty($favicon_url)) {
$custom_favicon = get_option("blog_public")
? get_site_icon_url()
: "";
if ($custom_favicon) {
$favicon_url = $custom_favicon;
}
}
if (empty($favicon_url)) {
$site_url = get_site_url();
$favicon_url = $site_url . "/favicon.ico";
$response = wp_remote_head($favicon_url, [
"timeout" => 5,
"sslverify" => false,
]);
if (
is_wp_error($response) ||
wp_remote_retrieve_response_code($response) !== 200
) {
$favicon_url = includes_url("images/w-logo-blue.png");
}
}
restore_current_blog();
wp_cache_set($cache_key, $favicon_url, $this->cache_group, 3600);
return $favicon_url;
}
private function fetch_active_sites($limit)
{
return get_sites([
"number" => $limit,
"orderby" => "last_updated",
"order" => "DESC",
"public" => null,
"archived" => 0,
"mature" => 0,
"spam" => 0,
"deleted" => 0,
]);
}
public function get_top_storage_sites($limit = 5)
{
$storage_data = $this->get_storage_usage_data($limit);
return array_slice($storage_data["sites"], 0, $limit);
}
private function get_site_storage_usage($blog_id)
{
$cache_key = "storage_{$blog_id}";
$cached = wp_cache_get($cache_key, $this->cache_group);
if ($cached !== false) {
return $cached;
}
$upload_dir = $this->get_site_upload_dir($blog_id);
$size_bytes = 0;
if (is_dir($upload_dir)) {
$size_bytes = $this->calculate_directory_size($upload_dir);
}
wp_cache_set($cache_key, $size_bytes, $this->cache_group, 3600);
return $size_bytes;
}
private function get_site_upload_dir($blog_id)
{
if ($blog_id == 1) {
$upload_dir = wp_upload_dir();
return $upload_dir["basedir"];
}
$upload_dir = wp_upload_dir();
$base_dir = $upload_dir["basedir"];
if (is_subdomain_install()) {
return $base_dir . "/sites/" . $blog_id;
} else {
return str_replace("/sites/1/", "/sites/{$blog_id}/", $base_dir);
}
}
private function calculate_directory_size($directory)
{
$size = 0;
$file_count = 0;
$max_files = 5000;
try {
if (!is_readable($directory)) {
return 0;
}
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator(
$directory,
RecursiveDirectoryIterator::SKIP_DOTS
),
RecursiveIteratorIterator::LEAVES_ONLY
);
foreach ($iterator as $file) {
if ($file->isFile() && $file->isReadable()) {
$size += $file->getSize();
$file_count++;
if ($file_count > $max_files) {
break;
}
}
}
} catch (Exception $e) {
error_log("MSD Storage calculation error: " . $e->getMessage());
return 0;
}
return $size;
}
private function get_site_name($blog_id)
{
$cache_key = "site_name_{$blog_id}";
$cached = wp_cache_get($cache_key, $this->cache_group);
if ($cached !== false) {
return $cached;
}
$site_details = get_blog_details($blog_id);
$name = $site_details
? $site_details->blogname
: __("Unknown Site", "wp-multisite-dashboard");
wp_cache_set($cache_key, $name, $this->cache_group, 3600);
return $name;
}
private function get_site_user_count($blog_id)
{
$cache_key = "user_count_{$blog_id}";
$cached = wp_cache_get($cache_key, $this->cache_group);
if ($cached !== false) {
return $cached;
}
$count = count_users()["total_users"];
if ($blog_id > 1) {
$users = get_users(["blog_id" => $blog_id, "fields" => "ID"]);
$count = count($users);
}
wp_cache_set($cache_key, $count, $this->cache_group, 1800);
return $count;
}
private function get_site_last_activity($blog_id)
{
$cache_key = "last_activity_{$blog_id}";
$cached = wp_cache_get($cache_key, $this->cache_group);
if ($cached !== false) {
return $cached;
}
switch_to_blog($blog_id);
$last_post = get_posts([
"numberposts" => 1,
"post_status" => "publish",
"orderby" => "date",
"order" => "DESC",
"fields" => "post_date",
]);
$last_comment = get_comments([
"number" => 1,
"status" => "approve",
"orderby" => "comment_date",
"order" => "DESC",
"fields" => "comment_date",
]);
restore_current_blog();
$dates = array_filter([
$last_post ? $last_post[0]->post_date : null,
$last_comment ? $last_comment[0]->comment_date : null,
]);
$last_activity = empty($dates) ? null : max($dates);
wp_cache_set($cache_key, $last_activity, $this->cache_group, 1800);
return $last_activity;
}
private function get_site_status($blog_id)
{
$last_activity = $this->get_site_last_activity($blog_id);
if (empty($last_activity)) {
return "inactive";
}
$days_inactive =
(current_time("timestamp") - strtotime($last_activity)) /
DAY_IN_SECONDS;
if ($days_inactive > 90) {
return "inactive";
} elseif ($days_inactive > 30) {
return "warning";
}
return "active";
}
public function log_activity(
$site_id,
$activity_type,
$description,
$severity = "low",
$user_id = null
) {
$table_name = $this->wpdb->base_prefix . "msd_activity_log";
$result = $this->wpdb->insert(
$table_name,
[
"site_id" => intval($site_id),
"user_id" => $user_id ?: get_current_user_id(),
"activity_type" => sanitize_text_field($activity_type),
"description" => sanitize_text_field($description),
"severity" => in_array($severity, [
"low",
"medium",
"high",
"critical",
])
? $severity
: "low",
"ip_address" => $this->get_client_ip(),
"user_agent" => substr(
$_SERVER["HTTP_USER_AGENT"] ?? "",
0,
500
),
"created_at" => current_time("mysql"),
],
["%d", "%d", "%s", "%s", "%s", "%s", "%s", "%s"]
);
if (mt_rand(1, 100) <= 5) {
$this->cleanup_old_activity_logs();
}
return $result !== false;
}
private function get_client_ip()
{
$ip_headers = [
"HTTP_CF_CONNECTING_IP",
"HTTP_CLIENT_IP",
"HTTP_X_FORWARDED_FOR",
"HTTP_X_FORWARDED",
"HTTP_X_CLUSTER_CLIENT_IP",
"HTTP_FORWARDED_FOR",
"HTTP_FORWARDED",
"REMOTE_ADDR",
];
foreach ($ip_headers as $header) {
if (isset($_SERVER[$header]) && !empty($_SERVER[$header])) {
$ips = explode(",", $_SERVER[$header]);
$ip = trim($ips[0]);
if (
filter_var(
$ip,
FILTER_VALIDATE_IP,
FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE
)
) {
return $ip;
}
}
}
return $_SERVER["REMOTE_ADDR"] ?? "";
}
private function cleanup_old_activity_logs()
{
$this->wpdb->query(
$this->wpdb->prepare(
"DELETE FROM {$this->wpdb->base_prefix}msd_activity_log
WHERE created_at < %s",
date("Y-m-d H:i:s", strtotime("-60 days"))
)
);
}
public function create_activity_log_table()
{
$table_name = $this->wpdb->base_prefix . "msd_activity_log";
$charset_collate = $this->wpdb->get_charset_collate();
$sql = "CREATE TABLE IF NOT EXISTS $table_name (
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
site_id bigint(20) unsigned NOT NULL,
user_id bigint(20) unsigned NOT NULL,
activity_type varchar(50) NOT NULL,
description text NOT NULL,
severity enum('low','medium','high','critical') DEFAULT 'low',
ip_address varchar(45) DEFAULT '',
user_agent text DEFAULT '',
created_at datetime NOT NULL,
PRIMARY KEY (id),
KEY site_id (site_id),
KEY user_id (user_id),
KEY activity_type (activity_type),
KEY severity (severity),
KEY created_at (created_at),
KEY site_activity (site_id, created_at)
) $charset_collate;";
require_once ABSPATH . "wp-admin/includes/upgrade.php";
dbDelta($sql);
}
public function clear_all_caches()
{
wp_cache_flush_group($this->cache_group);
$transients = [
"msd_total_sites",
"msd_total_users",
"msd_total_posts",
"msd_total_pages",
"msd_total_storage",
"msd_storage_usage_data",
"msd_network_status",
"msd_network_settings_overview",
"msd_multisite_configuration",
"msd_network_information",
"msd_recent_network_activity",
];
foreach ($transients as $transient) {
delete_site_transient($transient);
}
return true;
}
public function clear_widget_cache($widget = null)
{
if ($widget) {
$cache_keys = [
"network_overview" => [
"total_posts",
"total_pages",
"multisite_configuration",
"network_information",
"network_status",
],
"site_list" => ["recent_sites_5"],
"storage_data" => ["storage_usage_data_5"],
"network_settings" => ["network_settings_overview"],
"user_management" => ["recent_users_data"],
"last_edits" => ["recent_network_activity_10"],
"contact_info" => ["contact_info"],
];
if (isset($cache_keys[$widget])) {
foreach ($cache_keys[$widget] as $key) {
$this->delete_cache($key);
}
}
} else {
$this->clear_all_caches();
}
return true;
}
private function get_cache($key)
{
return get_site_transient("msd_{$key}");
}
private function set_cache($key, $value, $expiration = null)
{
$expiration = $expiration ?: $this->cache_timeout;
return set_site_transient("msd_{$key}", $value, $expiration);
}
private function delete_cache($key)
{
return delete_site_transient("msd_{$key}");
}
public function get_network_stats_summary()
{
return [
"total_sites" => $this->get_total_sites(),
"total_users" => $this->get_total_users(),
"total_posts" => $this->get_total_posts(),
"total_pages" => $this->get_total_pages(),
"total_storage" => $this->get_total_storage_used(),
"last_updated" => current_time("mysql"),
];
}
}