mirror of
https://github.com/WPMultisite/wp-multisite-dashboard.git
synced 2025-08-03 11:11:31 +08:00
初版提交
文派多站点仪表盘
This commit is contained in:
parent
9f51fc0e76
commit
0ecad06f54
135 changed files with 21593 additions and 0 deletions
255
includes/admin-template.php
Normal file
255
includes/admin-template.php
Normal file
|
@ -0,0 +1,255 @@
|
|||
<?php
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class WP_MSD_Admin_Template {
|
||||
|
||||
public static function render_dashboard_notice($message, $type = 'info', $dismissible = true) {
|
||||
$classes = ['notice', "notice-{$type}"];
|
||||
|
||||
if ($dismissible) {
|
||||
$classes[] = 'is-dismissible';
|
||||
}
|
||||
|
||||
$class_string = implode(' ', $classes);
|
||||
|
||||
echo "<div class=\"{$class_string}\">";
|
||||
echo "<p>" . esc_html($message) . "</p>";
|
||||
echo "</div>";
|
||||
}
|
||||
|
||||
public static function render_widget_header($title, $widget_id = null, $show_refresh = true) {
|
||||
echo '<div class="msd-widget-header">';
|
||||
|
||||
if ($show_refresh && $widget_id) {
|
||||
echo '<button class="msd-refresh-btn" title="Refresh" data-widget="' . esc_attr($widget_id) . '">↻</button>';
|
||||
}
|
||||
|
||||
if ($title) {
|
||||
echo '<h3 class="msd-widget-title">' . esc_html($title) . '</h3>';
|
||||
}
|
||||
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
public static function render_loading_state($message = null) {
|
||||
$message = $message ?: __('Loading...', 'wp-multisite-dashboard');
|
||||
|
||||
echo '<div class="msd-loading">';
|
||||
echo '<span class="msd-spinner"></span>';
|
||||
echo esc_html($message);
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
public static function render_empty_state($message, $action_text = null, $action_url = null) {
|
||||
echo '<div class="msd-empty-state">';
|
||||
echo '<p>' . esc_html($message) . '</p>';
|
||||
|
||||
if ($action_text && $action_url) {
|
||||
echo '<a href="' . esc_url($action_url) . '" class="button button-primary">' . esc_html($action_text) . '</a>';
|
||||
}
|
||||
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
public static function render_error_state($message, $retry_action = null) {
|
||||
echo '<div class="msd-error-state">';
|
||||
echo '<p>' . esc_html($message) . '</p>';
|
||||
|
||||
if ($retry_action) {
|
||||
echo '<button class="button msd-retry-btn" onclick="' . esc_attr($retry_action) . '">Try Again</button>';
|
||||
}
|
||||
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
public static function get_priority_badge($priority) {
|
||||
$badges = [
|
||||
'low' => ['Low Priority', 'msd-priority-low'],
|
||||
'medium' => ['Medium Priority', 'msd-priority-medium'],
|
||||
'high' => ['High Priority', 'msd-priority-high']
|
||||
];
|
||||
|
||||
if (!isset($badges[$priority])) {
|
||||
$priority = 'medium';
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'<span class="msd-priority-badge %s">%s</span>',
|
||||
esc_attr($badges[$priority][1]),
|
||||
esc_html($badges[$priority][0])
|
||||
);
|
||||
}
|
||||
|
||||
public static function get_status_badge($status, $label = null) {
|
||||
$badges = [
|
||||
'active' => ['Active', 'msd-status-good'],
|
||||
'inactive' => ['Inactive', 'msd-status-warning'],
|
||||
'critical' => ['Critical', 'msd-status-critical'],
|
||||
'warning' => ['Warning', 'msd-status-warning'],
|
||||
'good' => ['Good', 'msd-status-good'],
|
||||
'neutral' => ['Neutral', 'msd-status-neutral']
|
||||
];
|
||||
|
||||
if (!isset($badges[$status])) {
|
||||
$status = 'neutral';
|
||||
}
|
||||
|
||||
$display_label = $label ?: $badges[$status][0];
|
||||
|
||||
return sprintf(
|
||||
'<span class="msd-status-badge %s">%s</span>',
|
||||
esc_attr($badges[$status][1]),
|
||||
esc_html($display_label)
|
||||
);
|
||||
}
|
||||
|
||||
public static function format_file_size($bytes) {
|
||||
if ($bytes >= 1073741824) {
|
||||
return number_format($bytes / 1073741824, 2) . ' GB';
|
||||
} elseif ($bytes >= 1048576) {
|
||||
return number_format($bytes / 1048576, 2) . ' MB';
|
||||
} elseif ($bytes >= 1024) {
|
||||
return number_format($bytes / 1024, 2) . ' KB';
|
||||
} else {
|
||||
return $bytes . ' bytes';
|
||||
}
|
||||
}
|
||||
|
||||
public static function format_time_ago($timestamp) {
|
||||
if (empty($timestamp)) {
|
||||
return __('Never', 'wp-multisite-dashboard');
|
||||
}
|
||||
|
||||
if (is_string($timestamp)) {
|
||||
$timestamp = strtotime($timestamp);
|
||||
}
|
||||
|
||||
if (!$timestamp) {
|
||||
return __('Unknown', 'wp-multisite-dashboard');
|
||||
}
|
||||
|
||||
return human_time_diff($timestamp) . ' ago';
|
||||
}
|
||||
|
||||
public static function render_progress_bar($percentage, $label = null, $status = 'good') {
|
||||
$percentage = max(0, min(100, intval($percentage)));
|
||||
$status_class = "msd-progress-{$status}";
|
||||
|
||||
echo '<div class="msd-progress-container">';
|
||||
|
||||
if ($label) {
|
||||
echo '<div class="msd-progress-label">' . esc_html($label) . '</div>';
|
||||
}
|
||||
|
||||
echo '<div class="msd-progress-bar">';
|
||||
echo '<div class="msd-progress-fill ' . esc_attr($status_class) . '" style="width: ' . $percentage . '%"></div>';
|
||||
echo '</div>';
|
||||
|
||||
echo '<div class="msd-progress-text">' . $percentage . '%</div>';
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
public static function render_data_table($headers, $rows, $empty_message = null) {
|
||||
if (empty($rows)) {
|
||||
if ($empty_message) {
|
||||
self::render_empty_state($empty_message);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
echo '<div class="msd-data-table-wrapper">';
|
||||
echo '<table class="msd-data-table">';
|
||||
|
||||
if (!empty($headers)) {
|
||||
echo '<thead><tr>';
|
||||
foreach ($headers as $header) {
|
||||
echo '<th>' . esc_html($header) . '</th>';
|
||||
}
|
||||
echo '</tr></thead>';
|
||||
}
|
||||
|
||||
echo '<tbody>';
|
||||
foreach ($rows as $row) {
|
||||
echo '<tr>';
|
||||
foreach ($row as $cell) {
|
||||
echo '<td>' . $cell . '</td>';
|
||||
}
|
||||
echo '</tr>';
|
||||
}
|
||||
echo '</tbody>';
|
||||
|
||||
echo '</table>';
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
public static function render_action_buttons($actions) {
|
||||
if (empty($actions)) {
|
||||
return;
|
||||
}
|
||||
|
||||
echo '<div class="msd-action-buttons">';
|
||||
|
||||
foreach ($actions as $action) {
|
||||
$class = 'button ' . ($action['primary'] ?? false ? 'button-primary' : 'button-secondary');
|
||||
$attributes = '';
|
||||
|
||||
if (!empty($action['attributes'])) {
|
||||
foreach ($action['attributes'] as $attr => $value) {
|
||||
$attributes .= ' ' . esc_attr($attr) . '="' . esc_attr($value) . '"';
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($action['url'])) {
|
||||
echo '<a href="' . esc_url($action['url']) . '" class="' . esc_attr($class) . '"' . $attributes . '>';
|
||||
echo esc_html($action['text']);
|
||||
echo '</a>';
|
||||
} else {
|
||||
echo '<button type="button" class="' . esc_attr($class) . '"' . $attributes . '>';
|
||||
echo esc_html($action['text']);
|
||||
echo '</button>';
|
||||
}
|
||||
}
|
||||
|
||||
echo '</div>';
|
||||
}
|
||||
|
||||
public static function sanitize_widget_data($data) {
|
||||
if (is_array($data)) {
|
||||
return array_map([self::class, 'sanitize_widget_data'], $data);
|
||||
}
|
||||
|
||||
if (is_string($data)) {
|
||||
return sanitize_text_field($data);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public static function validate_nonce($nonce, $action) {
|
||||
return wp_verify_nonce($nonce, $action);
|
||||
}
|
||||
|
||||
public static function can_manage_network() {
|
||||
return current_user_can('manage_network');
|
||||
}
|
||||
|
||||
public static function get_current_screen_id() {
|
||||
$screen = get_current_screen();
|
||||
return $screen ? $screen->id : '';
|
||||
}
|
||||
|
||||
public static function is_network_admin_page($page_slug = null) {
|
||||
if (!is_network_admin()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($page_slug) {
|
||||
return isset($_GET['page']) && $_GET['page'] === $page_slug;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
864
includes/class-network-data.php
Normal file
864
includes/class-network-data.php
Normal file
|
@ -0,0 +1,864 @@
|
|||
<?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',
|
||||
'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')
|
||||
];
|
||||
}
|
||||
}
|
518
includes/class-todo-manager.php
Normal file
518
includes/class-todo-manager.php
Normal file
|
@ -0,0 +1,518 @@
|
|||
<?php
|
||||
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
class WP_MSD_Todo_Manager {
|
||||
|
||||
private $wpdb;
|
||||
private $table_name;
|
||||
private $cache_group = 'msd_todos';
|
||||
|
||||
public function __construct() {
|
||||
global $wpdb;
|
||||
$this->wpdb = $wpdb;
|
||||
$this->table_name = $wpdb->base_prefix . 'msd_todo_list';
|
||||
}
|
||||
|
||||
public function create_todo_table() {
|
||||
$charset_collate = $this->wpdb->get_charset_collate();
|
||||
|
||||
$sql = "CREATE TABLE IF NOT EXISTS {$this->table_name} (
|
||||
id bigint(20) unsigned NOT NULL AUTO_INCREMENT,
|
||||
item varchar(500) NOT NULL,
|
||||
completed tinyint(1) DEFAULT 0,
|
||||
priority enum('low','medium','high') DEFAULT 'medium',
|
||||
user_id bigint(20) unsigned NOT NULL,
|
||||
created_at datetime NOT NULL,
|
||||
updated_at datetime DEFAULT NULL,
|
||||
due_date datetime DEFAULT NULL,
|
||||
PRIMARY KEY (id),
|
||||
KEY user_id (user_id),
|
||||
KEY completed (completed),
|
||||
KEY priority (priority),
|
||||
KEY created_at (created_at)
|
||||
) $charset_collate;";
|
||||
|
||||
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
|
||||
dbDelta($sql);
|
||||
}
|
||||
|
||||
public function get_todo_items($limit = 50, $user_id = null, $completed = null) {
|
||||
$cache_key = "todo_items_{$limit}_" . ($user_id ?? 'all') . "_" . ($completed ?? 'all');
|
||||
$cached = wp_cache_get($cache_key, $this->cache_group);
|
||||
|
||||
if ($cached !== false) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
$where_clauses = ['1=1'];
|
||||
$where_values = [];
|
||||
|
||||
if ($user_id !== null) {
|
||||
$where_clauses[] = 'user_id = %d';
|
||||
$where_values[] = $user_id;
|
||||
}
|
||||
|
||||
if ($completed !== null) {
|
||||
$where_clauses[] = 'completed = %d';
|
||||
$where_values[] = $completed ? 1 : 0;
|
||||
}
|
||||
|
||||
$where_sql = implode(' AND ', $where_clauses);
|
||||
|
||||
$sql = "SELECT * FROM {$this->table_name}
|
||||
WHERE {$where_sql}
|
||||
ORDER BY completed ASC, priority DESC, created_at DESC
|
||||
LIMIT %d";
|
||||
|
||||
$where_values[] = $limit;
|
||||
|
||||
$prepared_sql = $this->wpdb->prepare($sql, ...$where_values);
|
||||
$results = $this->wpdb->get_results($prepared_sql, ARRAY_A);
|
||||
|
||||
$todos = [];
|
||||
foreach ($results as $row) {
|
||||
$user_data = get_userdata($row['user_id']);
|
||||
$todos[] = [
|
||||
'id' => intval($row['id']),
|
||||
'item' => $row['item'],
|
||||
'completed' => (bool)$row['completed'],
|
||||
'priority' => $row['priority'],
|
||||
'user_id' => intval($row['user_id']),
|
||||
'user_name' => $user_data ? $user_data->display_name : 'Unknown User',
|
||||
'user_avatar' => $user_data ? get_avatar_url($user_data->ID, 32) : '',
|
||||
'created_at' => $row['created_at'],
|
||||
'updated_at' => $row['updated_at'],
|
||||
'due_date' => $row['due_date'],
|
||||
'created_human' => human_time_diff(strtotime($row['created_at'])) . ' ago',
|
||||
'due_human' => $row['due_date'] ? human_time_diff(strtotime($row['due_date'])) : null,
|
||||
'is_overdue' => $row['due_date'] && strtotime($row['due_date']) < current_time('timestamp') && !$row['completed'],
|
||||
'priority_label' => $this->get_priority_label($row['priority']),
|
||||
'priority_class' => $this->get_priority_class($row['priority'])
|
||||
];
|
||||
}
|
||||
|
||||
wp_cache_set($cache_key, $todos, $this->cache_group, 1800);
|
||||
return $todos;
|
||||
}
|
||||
|
||||
public function add_todo_item($item, $priority = 'medium', $user_id = null, $due_date = null) {
|
||||
if (empty($item)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$user_id = $user_id ?: get_current_user_id();
|
||||
|
||||
if (!$user_id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$data = [
|
||||
'item' => sanitize_text_field($item),
|
||||
'priority' => in_array($priority, ['low', 'medium', 'high']) ? $priority : 'medium',
|
||||
'user_id' => $user_id,
|
||||
'created_at' => current_time('mysql'),
|
||||
'due_date' => $due_date ? date('Y-m-d H:i:s', strtotime($due_date)) : null
|
||||
];
|
||||
|
||||
$result = $this->wpdb->insert(
|
||||
$this->table_name,
|
||||
$data,
|
||||
['%s', '%s', '%d', '%s', '%s']
|
||||
);
|
||||
|
||||
if ($result !== false) {
|
||||
$this->clear_cache();
|
||||
|
||||
if (class_exists('WP_MSD_Network_Data')) {
|
||||
$network_data = new WP_MSD_Network_Data();
|
||||
$network_data->log_activity(
|
||||
0,
|
||||
'todo_added',
|
||||
sprintf('Todo item added: %s', substr($item, 0, 50)),
|
||||
'low',
|
||||
$user_id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $result !== false;
|
||||
}
|
||||
|
||||
public function delete_todo_item($item_id, $user_id = null) {
|
||||
$item_id = intval($item_id);
|
||||
|
||||
if (!$item_id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$where = ['id' => $item_id];
|
||||
$where_format = ['%d'];
|
||||
|
||||
if ($user_id) {
|
||||
$where['user_id'] = $user_id;
|
||||
$where_format[] = '%d';
|
||||
} elseif (!current_user_can('manage_network')) {
|
||||
$where['user_id'] = get_current_user_id();
|
||||
$where_format[] = '%d';
|
||||
}
|
||||
|
||||
$item = $this->wpdb->get_row(
|
||||
$this->wpdb->prepare(
|
||||
"SELECT item FROM {$this->table_name} WHERE id = %d",
|
||||
$item_id
|
||||
)
|
||||
);
|
||||
|
||||
$result = $this->wpdb->delete($this->table_name, $where, $where_format);
|
||||
|
||||
if ($result !== false && $item) {
|
||||
$this->clear_cache();
|
||||
|
||||
if (class_exists('WP_MSD_Network_Data')) {
|
||||
$network_data = new WP_MSD_Network_Data();
|
||||
$network_data->log_activity(
|
||||
0,
|
||||
'todo_deleted',
|
||||
sprintf('Todo item deleted: %s', substr($item->item, 0, 50)),
|
||||
'low'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $result !== false;
|
||||
}
|
||||
|
||||
public function toggle_todo_item($item_id, $completed = null, $user_id = null) {
|
||||
$item_id = intval($item_id);
|
||||
|
||||
if (!$item_id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$where_clause = 'id = %d';
|
||||
$where_values = [$item_id];
|
||||
|
||||
if ($user_id) {
|
||||
$where_clause .= ' AND user_id = %d';
|
||||
$where_values[] = $user_id;
|
||||
} elseif (!current_user_can('manage_network')) {
|
||||
$where_clause .= ' AND user_id = %d';
|
||||
$where_values[] = get_current_user_id();
|
||||
}
|
||||
|
||||
if ($completed === null) {
|
||||
$current_item = $this->wpdb->get_row(
|
||||
$this->wpdb->prepare(
|
||||
"SELECT completed FROM {$this->table_name} WHERE {$where_clause}",
|
||||
...$where_values
|
||||
)
|
||||
);
|
||||
|
||||
if (!$current_item) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$completed = !$current_item->completed;
|
||||
}
|
||||
|
||||
$result = $this->wpdb->update(
|
||||
$this->table_name,
|
||||
[
|
||||
'completed' => $completed ? 1 : 0,
|
||||
'updated_at' => current_time('mysql')
|
||||
],
|
||||
['id' => $item_id],
|
||||
['%d', '%s'],
|
||||
['%d']
|
||||
);
|
||||
|
||||
if ($result !== false) {
|
||||
$this->clear_cache();
|
||||
}
|
||||
|
||||
return $result !== false;
|
||||
}
|
||||
|
||||
public function update_todo_item($item_id, $data, $user_id = null) {
|
||||
$item_id = intval($item_id);
|
||||
|
||||
if (!$item_id) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$allowed_fields = ['item', 'priority', 'due_date', 'completed'];
|
||||
$update_data = [];
|
||||
$update_format = [];
|
||||
|
||||
foreach ($data as $field => $value) {
|
||||
if (!in_array($field, $allowed_fields)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch ($field) {
|
||||
case 'item':
|
||||
$update_data['item'] = sanitize_text_field($value);
|
||||
$update_format[] = '%s';
|
||||
break;
|
||||
case 'priority':
|
||||
if (in_array($value, ['low', 'medium', 'high'])) {
|
||||
$update_data['priority'] = $value;
|
||||
$update_format[] = '%s';
|
||||
}
|
||||
break;
|
||||
case 'due_date':
|
||||
$update_data['due_date'] = $value ? date('Y-m-d H:i:s', strtotime($value)) : null;
|
||||
$update_format[] = '%s';
|
||||
break;
|
||||
case 'completed':
|
||||
$update_data['completed'] = $value ? 1 : 0;
|
||||
$update_format[] = '%d';
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($update_data)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$update_data['updated_at'] = current_time('mysql');
|
||||
$update_format[] = '%s';
|
||||
|
||||
$where = ['id' => $item_id];
|
||||
$where_format = ['%d'];
|
||||
|
||||
if ($user_id) {
|
||||
$where['user_id'] = $user_id;
|
||||
$where_format[] = '%d';
|
||||
} elseif (!current_user_can('manage_network')) {
|
||||
$where['user_id'] = get_current_user_id();
|
||||
$where_format[] = '%d';
|
||||
}
|
||||
|
||||
$result = $this->wpdb->update(
|
||||
$this->table_name,
|
||||
$update_data,
|
||||
$where,
|
||||
$update_format,
|
||||
$where_format
|
||||
);
|
||||
|
||||
if ($result !== false) {
|
||||
$this->clear_cache();
|
||||
}
|
||||
|
||||
return $result !== false;
|
||||
}
|
||||
|
||||
public function get_todo_statistics() {
|
||||
$cache_key = 'todo_statistics';
|
||||
$cached = wp_cache_get($cache_key, $this->cache_group);
|
||||
|
||||
if ($cached !== false) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
$stats = [
|
||||
'total' => 0,
|
||||
'completed' => 0,
|
||||
'pending' => 0,
|
||||
'overdue' => 0,
|
||||
'by_priority' => [
|
||||
'high' => 0,
|
||||
'medium' => 0,
|
||||
'low' => 0
|
||||
],
|
||||
'by_user' => []
|
||||
];
|
||||
|
||||
$total_result = $this->wpdb->get_row(
|
||||
"SELECT
|
||||
COUNT(*) as total,
|
||||
SUM(completed) as completed,
|
||||
SUM(CASE WHEN completed = 0 THEN 1 ELSE 0 END) as pending,
|
||||
SUM(CASE WHEN due_date < NOW() AND completed = 0 THEN 1 ELSE 0 END) as overdue
|
||||
FROM {$this->table_name}"
|
||||
);
|
||||
|
||||
if ($total_result) {
|
||||
$stats['total'] = intval($total_result->total);
|
||||
$stats['completed'] = intval($total_result->completed);
|
||||
$stats['pending'] = intval($total_result->pending);
|
||||
$stats['overdue'] = intval($total_result->overdue);
|
||||
}
|
||||
|
||||
$priority_results = $this->wpdb->get_results(
|
||||
"SELECT priority, COUNT(*) as count
|
||||
FROM {$this->table_name}
|
||||
WHERE completed = 0
|
||||
GROUP BY priority"
|
||||
);
|
||||
|
||||
foreach ($priority_results as $priority_result) {
|
||||
if (isset($stats['by_priority'][$priority_result->priority])) {
|
||||
$stats['by_priority'][$priority_result->priority] = intval($priority_result->count);
|
||||
}
|
||||
}
|
||||
|
||||
$user_results = $this->wpdb->get_results(
|
||||
"SELECT user_id, COUNT(*) as total, SUM(completed) as completed
|
||||
FROM {$this->table_name}
|
||||
GROUP BY user_id
|
||||
ORDER BY total DESC
|
||||
LIMIT 10"
|
||||
);
|
||||
|
||||
foreach ($user_results as $user_result) {
|
||||
$user_data = get_userdata($user_result->user_id);
|
||||
$stats['by_user'][] = [
|
||||
'user_id' => intval($user_result->user_id),
|
||||
'user_name' => $user_data ? $user_data->display_name : 'Unknown User',
|
||||
'total' => intval($user_result->total),
|
||||
'completed' => intval($user_result->completed),
|
||||
'pending' => intval($user_result->total) - intval($user_result->completed)
|
||||
];
|
||||
}
|
||||
|
||||
wp_cache_set($cache_key, $stats, $this->cache_group, 3600);
|
||||
return $stats;
|
||||
}
|
||||
|
||||
public function get_user_todos($user_id, $limit = 20) {
|
||||
return $this->get_todo_items($limit, $user_id);
|
||||
}
|
||||
|
||||
public function get_overdue_todos($limit = 20) {
|
||||
$cache_key = "overdue_todos_{$limit}";
|
||||
$cached = wp_cache_get($cache_key, $this->cache_group);
|
||||
|
||||
if ($cached !== false) {
|
||||
return $cached;
|
||||
}
|
||||
|
||||
$sql = "SELECT * FROM {$this->table_name}
|
||||
WHERE due_date < NOW()
|
||||
AND completed = 0
|
||||
ORDER BY due_date ASC
|
||||
LIMIT %d";
|
||||
|
||||
$results = $this->wpdb->get_results(
|
||||
$this->wpdb->prepare($sql, $limit),
|
||||
ARRAY_A
|
||||
);
|
||||
|
||||
$todos = [];
|
||||
foreach ($results as $row) {
|
||||
$user_data = get_userdata($row['user_id']);
|
||||
$todos[] = [
|
||||
'id' => intval($row['id']),
|
||||
'item' => $row['item'],
|
||||
'priority' => $row['priority'],
|
||||
'user_id' => intval($row['user_id']),
|
||||
'user_name' => $user_data ? $user_data->display_name : 'Unknown User',
|
||||
'due_date' => $row['due_date'],
|
||||
'due_human' => human_time_diff(strtotime($row['due_date'])) . ' overdue',
|
||||
'created_at' => $row['created_at'],
|
||||
'priority_label' => $this->get_priority_label($row['priority']),
|
||||
'priority_class' => $this->get_priority_class($row['priority'])
|
||||
];
|
||||
}
|
||||
|
||||
wp_cache_set($cache_key, $todos, $this->cache_group, 1800);
|
||||
return $todos;
|
||||
}
|
||||
|
||||
public function bulk_delete_completed($user_id = null) {
|
||||
$where_clause = 'completed = 1';
|
||||
$where_values = [];
|
||||
|
||||
if ($user_id) {
|
||||
$where_clause .= ' AND user_id = %d';
|
||||
$where_values[] = $user_id;
|
||||
} elseif (!current_user_can('manage_network')) {
|
||||
$where_clause .= ' AND user_id = %d';
|
||||
$where_values[] = get_current_user_id();
|
||||
}
|
||||
|
||||
if (!empty($where_values)) {
|
||||
$sql = "DELETE FROM {$this->table_name} WHERE {$where_clause}";
|
||||
$result = $this->wpdb->query($this->wpdb->prepare($sql, ...$where_values));
|
||||
} else {
|
||||
$result = $this->wpdb->delete($this->table_name, ['completed' => 1], ['%d']);
|
||||
}
|
||||
|
||||
if ($result !== false) {
|
||||
$this->clear_cache();
|
||||
|
||||
if (class_exists('WP_MSD_Network_Data')) {
|
||||
$network_data = new WP_MSD_Network_Data();
|
||||
$network_data->log_activity(
|
||||
0,
|
||||
'todo_bulk_delete',
|
||||
sprintf('Bulk deleted %d completed todo items', $result),
|
||||
'medium'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $result !== false;
|
||||
}
|
||||
|
||||
private function get_priority_label($priority) {
|
||||
$labels = [
|
||||
'low' => __('Low', 'wp-multisite-dashboard'),
|
||||
'medium' => __('Medium', 'wp-multisite-dashboard'),
|
||||
'high' => __('High', 'wp-multisite-dashboard')
|
||||
];
|
||||
return $labels[$priority] ?? $labels['medium'];
|
||||
}
|
||||
|
||||
private function get_priority_class($priority) {
|
||||
$classes = [
|
||||
'low' => 'msd-priority-low',
|
||||
'medium' => 'msd-priority-medium',
|
||||
'high' => 'msd-priority-high'
|
||||
];
|
||||
return $classes[$priority] ?? $classes['medium'];
|
||||
}
|
||||
|
||||
public function clear_cache() {
|
||||
wp_cache_flush_group($this->cache_group);
|
||||
return true;
|
||||
}
|
||||
|
||||
public function cleanup_old_todos($days = 90) {
|
||||
if (!current_user_can('manage_network')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$cutoff_date = date('Y-m-d H:i:s', strtotime("-{$days} days"));
|
||||
|
||||
$result = $this->wpdb->delete(
|
||||
$this->table_name,
|
||||
[
|
||||
'completed' => 1,
|
||||
'updated_at' => $cutoff_date
|
||||
],
|
||||
['%d', '%s']
|
||||
);
|
||||
|
||||
if ($result !== false) {
|
||||
$this->clear_cache();
|
||||
|
||||
if (class_exists('WP_MSD_Network_Data')) {
|
||||
$network_data = new WP_MSD_Network_Data();
|
||||
$network_data->log_activity(
|
||||
0,
|
||||
'todo_cleanup',
|
||||
sprintf('Cleaned up %d old completed todo items', $result),
|
||||
'medium'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $result !== false;
|
||||
}
|
||||
}
|
1061
includes/class-user-manager.php
Normal file
1061
includes/class-user-manager.php
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue