Files

326 lines
9.2 KiB
PHP
Raw Permalink Normal View History

<?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 ) . '"';
}
}