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', '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', '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' : __('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') ]; } }