## ✅ Activity Log System - Complete
### Backend Implementation
**1. Database Table**
- `ActivityLogTable.php` - Table creation and management
- Auto-creates on plugin init
- Indexed for performance (user_id, action, object, created_at)
**2. Logger Class**
- `Logger.php` - Main logging functionality
- `log()` - Log activities
- `get_activities()` - Query with filters
- `get_stats()` - Activity statistics
- `cleanup()` - Delete old logs
**3. REST API**
- `ActivityLogController.php` - REST endpoints
- GET `/activity-log` - List activities
- POST `/activity-log` - Create activity
- GET `/activity-log/stats` - Get statistics
### Features
**Logging:**
- User ID and name
- Action type (order.created, product.updated, etc.)
- Object type and ID
- Object name (auto-resolved)
- Description
- Metadata (JSON)
- IP address
- User agent
- Timestamp
**Querying:**
- Pagination
- Filter by action, object, user, date
- Search by description, object name, user name
- Sort by date (newest first)
**Statistics:**
- Total activities
- By action (top 10)
- By user (top 10)
- Date range filtering
### Activity Types
**Orders:**
- order.created, order.updated, order.status_changed
- order.payment_completed, order.refunded, order.deleted
**Products:**
- product.created, product.updated
- product.stock_changed, product.deleted
**Customers:**
- customer.created, customer.updated, customer.deleted
**Notifications:**
- notification.sent, notification.failed, notification.clicked
**Settings:**
- settings.updated, channel.toggled, event.toggled
### Integration
- Registered in Bootstrap
- REST API routes registered
- Ready for WooCommerce hooks
- Ready for frontend UI
---
**Next:** Frontend UI + WooCommerce hooks
288 lines
7.3 KiB
PHP
288 lines
7.3 KiB
PHP
<?php
|
|
/**
|
|
* Activity Logger
|
|
*
|
|
* Logs user activities and system events.
|
|
*
|
|
* @package WooNooW\Core\ActivityLog
|
|
*/
|
|
|
|
namespace WooNooW\Core\ActivityLog;
|
|
|
|
class Logger {
|
|
|
|
/**
|
|
* Log an activity
|
|
*
|
|
* @param string $action Action type (e.g., 'order.created')
|
|
* @param string $object_type Object type (e.g., 'order', 'product')
|
|
* @param int $object_id Object ID
|
|
* @param string $description Human-readable description
|
|
* @param array $metadata Additional metadata
|
|
* @param int|null $user_id User ID (null = current user)
|
|
* @return int|false Activity ID or false on failure
|
|
*/
|
|
public static function log($action, $object_type, $object_id, $description = '', $metadata = [], $user_id = null) {
|
|
global $wpdb;
|
|
|
|
// Get user info
|
|
if ($user_id === null) {
|
|
$user_id = get_current_user_id();
|
|
}
|
|
|
|
$user = get_userdata($user_id);
|
|
$user_name = $user ? $user->display_name : __('System', 'woonoow');
|
|
|
|
// Get object name
|
|
$object_name = self::get_object_name($object_type, $object_id);
|
|
|
|
// Get IP and user agent
|
|
$ip_address = self::get_client_ip();
|
|
$user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : '';
|
|
|
|
// Prepare data
|
|
$data = [
|
|
'user_id' => $user_id,
|
|
'user_name' => $user_name,
|
|
'action' => $action,
|
|
'object_type' => $object_type,
|
|
'object_id' => $object_id,
|
|
'object_name' => $object_name,
|
|
'description' => $description,
|
|
'metadata' => json_encode($metadata),
|
|
'ip_address' => $ip_address,
|
|
'user_agent' => $user_agent,
|
|
'created_at' => current_time('mysql'),
|
|
];
|
|
|
|
// Insert
|
|
$table_name = ActivityLogTable::get_table_name();
|
|
$result = $wpdb->insert($table_name, $data);
|
|
|
|
if ($result === false) {
|
|
return false;
|
|
}
|
|
|
|
return $wpdb->insert_id;
|
|
}
|
|
|
|
/**
|
|
* Get object name based on type and ID
|
|
*
|
|
* @param string $object_type
|
|
* @param int $object_id
|
|
* @return string
|
|
*/
|
|
private static function get_object_name($object_type, $object_id) {
|
|
switch ($object_type) {
|
|
case 'order':
|
|
return sprintf(__('Order #%d', 'woonoow'), $object_id);
|
|
|
|
case 'product':
|
|
$product = wc_get_product($object_id);
|
|
return $product ? $product->get_name() : sprintf(__('Product #%d', 'woonoow'), $object_id);
|
|
|
|
case 'customer':
|
|
$customer = get_userdata($object_id);
|
|
return $customer ? $customer->display_name : sprintf(__('Customer #%d', 'woonoow'), $object_id);
|
|
|
|
case 'notification':
|
|
return sprintf(__('Notification #%d', 'woonoow'), $object_id);
|
|
|
|
case 'settings':
|
|
return __('Settings', 'woonoow');
|
|
|
|
default:
|
|
return sprintf(__('%s #%d', 'woonoow'), ucfirst($object_type), $object_id);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get client IP address
|
|
*
|
|
* @return string
|
|
*/
|
|
private static function get_client_ip() {
|
|
$ip_keys = ['HTTP_CLIENT_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_FORWARDED', 'HTTP_X_CLUSTER_CLIENT_IP', 'HTTP_FORWARDED_FOR', 'HTTP_FORWARDED', 'REMOTE_ADDR'];
|
|
|
|
foreach ($ip_keys as $key) {
|
|
if (isset($_SERVER[$key]) && filter_var($_SERVER[$key], FILTER_VALIDATE_IP)) {
|
|
return $_SERVER[$key];
|
|
}
|
|
}
|
|
|
|
return '0.0.0.0';
|
|
}
|
|
|
|
/**
|
|
* Get activities
|
|
*
|
|
* @param array $args Query arguments
|
|
* @return array
|
|
*/
|
|
public static function get_activities($args = []) {
|
|
global $wpdb;
|
|
|
|
$defaults = [
|
|
'page' => 1,
|
|
'per_page' => 20,
|
|
'action' => null,
|
|
'object_type' => null,
|
|
'object_id' => null,
|
|
'user_id' => null,
|
|
'date_from' => null,
|
|
'date_to' => null,
|
|
'search' => null,
|
|
];
|
|
|
|
$args = wp_parse_args($args, $defaults);
|
|
|
|
$table_name = ActivityLogTable::get_table_name();
|
|
$where = ['1=1'];
|
|
$where_values = [];
|
|
|
|
// Filters
|
|
if ($args['action']) {
|
|
$where[] = 'action = %s';
|
|
$where_values[] = $args['action'];
|
|
}
|
|
|
|
if ($args['object_type']) {
|
|
$where[] = 'object_type = %s';
|
|
$where_values[] = $args['object_type'];
|
|
}
|
|
|
|
if ($args['object_id']) {
|
|
$where[] = 'object_id = %d';
|
|
$where_values[] = $args['object_id'];
|
|
}
|
|
|
|
if ($args['user_id']) {
|
|
$where[] = 'user_id = %d';
|
|
$where_values[] = $args['user_id'];
|
|
}
|
|
|
|
if ($args['date_from']) {
|
|
$where[] = 'created_at >= %s';
|
|
$where_values[] = $args['date_from'];
|
|
}
|
|
|
|
if ($args['date_to']) {
|
|
$where[] = 'created_at <= %s';
|
|
$where_values[] = $args['date_to'];
|
|
}
|
|
|
|
if ($args['search']) {
|
|
$where[] = '(description LIKE %s OR object_name LIKE %s OR user_name LIKE %s)';
|
|
$search_term = '%' . $wpdb->esc_like($args['search']) . '%';
|
|
$where_values[] = $search_term;
|
|
$where_values[] = $search_term;
|
|
$where_values[] = $search_term;
|
|
}
|
|
|
|
// Build query
|
|
$where_clause = implode(' AND ', $where);
|
|
$offset = ($args['page'] - 1) * $args['per_page'];
|
|
|
|
// Count total
|
|
$count_query = "SELECT COUNT(*) FROM {$table_name} WHERE {$where_clause}";
|
|
if (!empty($where_values)) {
|
|
$count_query = $wpdb->prepare($count_query, $where_values);
|
|
}
|
|
$total = (int) $wpdb->get_var($count_query);
|
|
|
|
// Get activities
|
|
$query = "SELECT * FROM {$table_name} WHERE {$where_clause} ORDER BY created_at DESC LIMIT %d OFFSET %d";
|
|
$where_values[] = $args['per_page'];
|
|
$where_values[] = $offset;
|
|
|
|
$query = $wpdb->prepare($query, $where_values);
|
|
$activities = $wpdb->get_results($query, ARRAY_A);
|
|
|
|
// Parse metadata
|
|
foreach ($activities as &$activity) {
|
|
$activity['metadata'] = json_decode($activity['metadata'], true);
|
|
}
|
|
|
|
return [
|
|
'activities' => $activities,
|
|
'total' => $total,
|
|
'page' => $args['page'],
|
|
'per_page' => $args['per_page'],
|
|
'total_pages' => ceil($total / $args['per_page']),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Get activity stats
|
|
*
|
|
* @param string $date_from
|
|
* @param string $date_to
|
|
* @return array
|
|
*/
|
|
public static function get_stats($date_from = null, $date_to = null) {
|
|
global $wpdb;
|
|
|
|
$table_name = ActivityLogTable::get_table_name();
|
|
$where = ['1=1'];
|
|
$where_values = [];
|
|
|
|
if ($date_from) {
|
|
$where[] = 'created_at >= %s';
|
|
$where_values[] = $date_from;
|
|
}
|
|
|
|
if ($date_to) {
|
|
$where[] = 'created_at <= %s';
|
|
$where_values[] = $date_to;
|
|
}
|
|
|
|
$where_clause = implode(' AND ', $where);
|
|
|
|
// Total activities
|
|
$total_query = "SELECT COUNT(*) FROM {$table_name} WHERE {$where_clause}";
|
|
if (!empty($where_values)) {
|
|
$total_query = $wpdb->prepare($total_query, $where_values);
|
|
}
|
|
$total = (int) $wpdb->get_var($total_query);
|
|
|
|
// By action
|
|
$by_action_query = "SELECT action, COUNT(*) as count FROM {$table_name} WHERE {$where_clause} GROUP BY action ORDER BY count DESC LIMIT 10";
|
|
if (!empty($where_values)) {
|
|
$by_action_query = $wpdb->prepare($by_action_query, $where_values);
|
|
}
|
|
$by_action = $wpdb->get_results($by_action_query, ARRAY_A);
|
|
|
|
// By user
|
|
$by_user_query = "SELECT user_id, user_name, COUNT(*) as count FROM {$table_name} WHERE {$where_clause} GROUP BY user_id ORDER BY count DESC LIMIT 10";
|
|
if (!empty($where_values)) {
|
|
$by_user_query = $wpdb->prepare($by_user_query, $where_values);
|
|
}
|
|
$by_user = $wpdb->get_results($by_user_query, ARRAY_A);
|
|
|
|
return [
|
|
'total' => $total,
|
|
'by_action' => $by_action,
|
|
'by_user' => $by_user,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Delete old activities
|
|
*
|
|
* @param int $days Days to keep
|
|
* @return int Number of deleted rows
|
|
*/
|
|
public static function cleanup($days = 90) {
|
|
global $wpdb;
|
|
|
|
$table_name = ActivityLogTable::get_table_name();
|
|
$date = date('Y-m-d H:i:s', strtotime("-{$days} days"));
|
|
|
|
return $wpdb->query($wpdb->prepare(
|
|
"DELETE FROM {$table_name} WHERE created_at < %s",
|
|
$date
|
|
));
|
|
}
|
|
}
|