326 lines
9.2 KiB
PHP
326 lines
9.2 KiB
PHP
|
|
<?php
|
||
|
|
/**
|
||
|
|
* Logger class for WooCommerce Business Central Integration
|
||
|
|
*
|
||
|
|
* @package WooBusinessCentral
|
||
|
|
*/
|
||
|
|
|
||
|
|
// Prevent direct access
|
||
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
||
|
|
exit;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Class WBC_Logger
|
||
|
|
*
|
||
|
|
* Handles logging to database with different severity levels.
|
||
|
|
*/
|
||
|
|
class WBC_Logger {
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Log level constants
|
||
|
|
*/
|
||
|
|
const DEBUG = 'DEBUG';
|
||
|
|
const INFO = 'INFO';
|
||
|
|
const WARNING = 'WARNING';
|
||
|
|
const ERROR = 'ERROR';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Database table name
|
||
|
|
*
|
||
|
|
* @var string
|
||
|
|
*/
|
||
|
|
private static $table_name = null;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Create the logs database table
|
||
|
|
*/
|
||
|
|
public static function create_table() {
|
||
|
|
global $wpdb;
|
||
|
|
|
||
|
|
$table_name = $wpdb->prefix . 'wbc_logs';
|
||
|
|
$charset_collate = $wpdb->get_charset_collate();
|
||
|
|
|
||
|
|
$sql = "CREATE TABLE IF NOT EXISTS $table_name (
|
||
|
|
id bigint(20) NOT NULL AUTO_INCREMENT,
|
||
|
|
timestamp datetime DEFAULT CURRENT_TIMESTAMP NOT NULL,
|
||
|
|
level varchar(20) NOT NULL,
|
||
|
|
context varchar(100) NOT NULL,
|
||
|
|
message text NOT NULL,
|
||
|
|
data longtext,
|
||
|
|
PRIMARY KEY (id),
|
||
|
|
KEY level (level),
|
||
|
|
KEY context (context),
|
||
|
|
KEY timestamp (timestamp)
|
||
|
|
) $charset_collate;";
|
||
|
|
|
||
|
|
require_once ABSPATH . 'wp-admin/includes/upgrade.php';
|
||
|
|
dbDelta( $sql );
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get the table name
|
||
|
|
*
|
||
|
|
* @return string
|
||
|
|
*/
|
||
|
|
private static function get_table_name() {
|
||
|
|
if ( self::$table_name === null ) {
|
||
|
|
global $wpdb;
|
||
|
|
self::$table_name = $wpdb->prefix . 'wbc_logs';
|
||
|
|
}
|
||
|
|
return self::$table_name;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Log a debug message
|
||
|
|
*
|
||
|
|
* @param string $context The context/source of the log.
|
||
|
|
* @param string $message The log message.
|
||
|
|
* @param mixed $data Optional. Additional data to log.
|
||
|
|
*/
|
||
|
|
public static function debug( $context, $message, $data = null ) {
|
||
|
|
self::log( self::DEBUG, $context, $message, $data );
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Log an info message
|
||
|
|
*
|
||
|
|
* @param string $context The context/source of the log.
|
||
|
|
* @param string $message The log message.
|
||
|
|
* @param mixed $data Optional. Additional data to log.
|
||
|
|
*/
|
||
|
|
public static function info( $context, $message, $data = null ) {
|
||
|
|
self::log( self::INFO, $context, $message, $data );
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Log a warning message
|
||
|
|
*
|
||
|
|
* @param string $context The context/source of the log.
|
||
|
|
* @param string $message The log message.
|
||
|
|
* @param mixed $data Optional. Additional data to log.
|
||
|
|
*/
|
||
|
|
public static function warning( $context, $message, $data = null ) {
|
||
|
|
self::log( self::WARNING, $context, $message, $data );
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Log an error message
|
||
|
|
*
|
||
|
|
* @param string $context The context/source of the log.
|
||
|
|
* @param string $message The log message.
|
||
|
|
* @param mixed $data Optional. Additional data to log.
|
||
|
|
*/
|
||
|
|
public static function error( $context, $message, $data = null ) {
|
||
|
|
self::log( self::ERROR, $context, $message, $data );
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Log a message to the database
|
||
|
|
*
|
||
|
|
* @param string $level The log level.
|
||
|
|
* @param string $context The context/source of the log.
|
||
|
|
* @param string $message The log message.
|
||
|
|
* @param mixed $data Optional. Additional data to log.
|
||
|
|
*/
|
||
|
|
private static function log( $level, $context, $message, $data = null ) {
|
||
|
|
global $wpdb;
|
||
|
|
|
||
|
|
$table_name = self::get_table_name();
|
||
|
|
|
||
|
|
// Prepare data for storage
|
||
|
|
$data_json = null;
|
||
|
|
if ( $data !== null ) {
|
||
|
|
$data_json = wp_json_encode( $data, JSON_PRETTY_PRINT );
|
||
|
|
}
|
||
|
|
|
||
|
|
// Insert log entry
|
||
|
|
$wpdb->insert(
|
||
|
|
$table_name,
|
||
|
|
array(
|
||
|
|
'timestamp' => current_time( 'mysql' ),
|
||
|
|
'level' => $level,
|
||
|
|
'context' => sanitize_text_field( $context ),
|
||
|
|
'message' => sanitize_textarea_field( $message ),
|
||
|
|
'data' => $data_json,
|
||
|
|
),
|
||
|
|
array( '%s', '%s', '%s', '%s', '%s' )
|
||
|
|
);
|
||
|
|
|
||
|
|
// Also log to WooCommerce logger if available
|
||
|
|
if ( function_exists( 'wc_get_logger' ) ) {
|
||
|
|
$wc_logger = wc_get_logger();
|
||
|
|
$log_level = strtolower( $level );
|
||
|
|
if ( method_exists( $wc_logger, $log_level ) ) {
|
||
|
|
$wc_message = "[$context] $message";
|
||
|
|
if ( $data !== null ) {
|
||
|
|
$wc_message .= ' | Data: ' . wp_json_encode( $data );
|
||
|
|
}
|
||
|
|
$wc_logger->$log_level( $wc_message, array( 'source' => 'woo-business-central' ) );
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get log entries
|
||
|
|
*
|
||
|
|
* @param array $args Query arguments.
|
||
|
|
* @return array Array of log entries.
|
||
|
|
*/
|
||
|
|
public static function get_logs( $args = array() ) {
|
||
|
|
global $wpdb;
|
||
|
|
|
||
|
|
$defaults = array(
|
||
|
|
'level' => '',
|
||
|
|
'context' => '',
|
||
|
|
'limit' => 100,
|
||
|
|
'offset' => 0,
|
||
|
|
'orderby' => 'timestamp',
|
||
|
|
'order' => 'DESC',
|
||
|
|
);
|
||
|
|
|
||
|
|
$args = wp_parse_args( $args, $defaults );
|
||
|
|
$table_name = self::get_table_name();
|
||
|
|
|
||
|
|
// Build WHERE clause
|
||
|
|
$where = array( '1=1' );
|
||
|
|
$values = array();
|
||
|
|
|
||
|
|
if ( ! empty( $args['level'] ) ) {
|
||
|
|
$where[] = 'level = %s';
|
||
|
|
$values[] = $args['level'];
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( ! empty( $args['context'] ) ) {
|
||
|
|
$where[] = 'context = %s';
|
||
|
|
$values[] = $args['context'];
|
||
|
|
}
|
||
|
|
|
||
|
|
$where_clause = implode( ' AND ', $where );
|
||
|
|
|
||
|
|
// Sanitize orderby and order
|
||
|
|
$allowed_orderby = array( 'id', 'timestamp', 'level', 'context' );
|
||
|
|
$orderby = in_array( $args['orderby'], $allowed_orderby, true ) ? $args['orderby'] : 'timestamp';
|
||
|
|
$order = strtoupper( $args['order'] ) === 'ASC' ? 'ASC' : 'DESC';
|
||
|
|
|
||
|
|
// Build query
|
||
|
|
$limit = absint( $args['limit'] );
|
||
|
|
$offset = absint( $args['offset'] );
|
||
|
|
|
||
|
|
$sql = "SELECT * FROM $table_name WHERE $where_clause ORDER BY $orderby $order LIMIT $limit OFFSET $offset";
|
||
|
|
|
||
|
|
if ( ! empty( $values ) ) {
|
||
|
|
$sql = $wpdb->prepare( $sql, $values );
|
||
|
|
}
|
||
|
|
|
||
|
|
return $wpdb->get_results( $sql, ARRAY_A );
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get total log count
|
||
|
|
*
|
||
|
|
* @param array $args Query arguments.
|
||
|
|
* @return int Total count.
|
||
|
|
*/
|
||
|
|
public static function get_log_count( $args = array() ) {
|
||
|
|
global $wpdb;
|
||
|
|
|
||
|
|
$table_name = self::get_table_name();
|
||
|
|
|
||
|
|
// Build WHERE clause
|
||
|
|
$where = array( '1=1' );
|
||
|
|
$values = array();
|
||
|
|
|
||
|
|
if ( ! empty( $args['level'] ) ) {
|
||
|
|
$where[] = 'level = %s';
|
||
|
|
$values[] = $args['level'];
|
||
|
|
}
|
||
|
|
|
||
|
|
if ( ! empty( $args['context'] ) ) {
|
||
|
|
$where[] = 'context = %s';
|
||
|
|
$values[] = $args['context'];
|
||
|
|
}
|
||
|
|
|
||
|
|
$where_clause = implode( ' AND ', $where );
|
||
|
|
|
||
|
|
$sql = "SELECT COUNT(*) FROM $table_name WHERE $where_clause";
|
||
|
|
|
||
|
|
if ( ! empty( $values ) ) {
|
||
|
|
$sql = $wpdb->prepare( $sql, $values );
|
||
|
|
}
|
||
|
|
|
||
|
|
return (int) $wpdb->get_var( $sql );
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Clear all logs
|
||
|
|
*
|
||
|
|
* @return bool|int Number of rows deleted or false on error.
|
||
|
|
*/
|
||
|
|
public static function clear_logs() {
|
||
|
|
global $wpdb;
|
||
|
|
$table_name = self::get_table_name();
|
||
|
|
return $wpdb->query( "TRUNCATE TABLE $table_name" );
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Delete old logs
|
||
|
|
*
|
||
|
|
* @param int $days Number of days to keep logs.
|
||
|
|
* @return int Number of rows deleted.
|
||
|
|
*/
|
||
|
|
public static function cleanup_old_logs( $days = 30 ) {
|
||
|
|
global $wpdb;
|
||
|
|
|
||
|
|
$table_name = self::get_table_name();
|
||
|
|
$cutoff_date = gmdate( 'Y-m-d H:i:s', strtotime( "-{$days} days" ) );
|
||
|
|
|
||
|
|
return $wpdb->query(
|
||
|
|
$wpdb->prepare(
|
||
|
|
"DELETE FROM $table_name WHERE timestamp < %s",
|
||
|
|
$cutoff_date
|
||
|
|
)
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Export logs to CSV format
|
||
|
|
*
|
||
|
|
* @param array $args Query arguments.
|
||
|
|
* @return string CSV content.
|
||
|
|
*/
|
||
|
|
public static function export_to_csv( $args = array() ) {
|
||
|
|
$logs = self::get_logs( array_merge( $args, array( 'limit' => 10000 ) ) );
|
||
|
|
|
||
|
|
$output = "ID,Timestamp,Level,Context,Message,Data\n";
|
||
|
|
|
||
|
|
foreach ( $logs as $log ) {
|
||
|
|
$row = array(
|
||
|
|
$log['id'],
|
||
|
|
$log['timestamp'],
|
||
|
|
$log['level'],
|
||
|
|
self::sanitize_csv_field( $log['context'] ),
|
||
|
|
self::sanitize_csv_field( $log['message'] ),
|
||
|
|
self::sanitize_csv_field( $log['data'] ?? '' ),
|
||
|
|
);
|
||
|
|
$output .= implode( ',', $row ) . "\n";
|
||
|
|
}
|
||
|
|
|
||
|
|
return $output;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Sanitize a field value for CSV export to prevent formula injection
|
||
|
|
*
|
||
|
|
* @param string $value The value to sanitize.
|
||
|
|
* @return string Sanitized and quoted CSV value.
|
||
|
|
*/
|
||
|
|
private static function sanitize_csv_field( $value ) {
|
||
|
|
// Prevent formula injection by prefixing dangerous characters with a single quote
|
||
|
|
if ( preg_match( '/^[=+\-@\t\r]/', $value ) ) {
|
||
|
|
$value = "'" . $value;
|
||
|
|
}
|
||
|
|
return '"' . str_replace( '"', '""', $value ) . '"';
|
||
|
|
}
|
||
|
|
}
|