mirror of
https://github.com/WPMultisite/wp-multisite-dashboard.git
synced 2025-08-03 19:19:51 +08:00
518 lines
16 KiB
PHP
518 lines
16 KiB
PHP
<?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;
|
|
}
|
|
}
|