feat: Update log creation to support schema changes and enhance compatibility

- Updated the `create_log` method to handle the new `time` (DATETIME) and `time_only` (TIME) columns.
- Ensured sanitized input for `license_key`, `action`, and `origin`.
- Improved error handling with detailed success and failure logs.
- Streamlined origin determination for better readability.
- Added example usage and database insertion validation.
This commit is contained in:
Michel 2025-01-05 11:49:59 -05:00
parent bf5734be57
commit 4f0405bf2d
6 changed files with 196 additions and 102 deletions

View file

@ -192,45 +192,84 @@ function slm_add_licenses_menu()


case 'activity': ?> case 'activity': ?>


<?php
// Retrieve the license key for the current record
$license_key = esc_attr($data['license_key']);


// Fetch the log data using a utility function to handle the database query
$log_entries = SLM_Helper_Class::get_license_logs($license_key);


// Display the log table if there are any log entries <?php
if ($log_entries) { global $wpdb;
?> $table_name = SLM_TBL_LIC_LOG;
<div class="wrap">
<h2><?php esc_html_e('Activity Log', 'slm-plus'); ?></h2> // Preserve existing query parameters
<table class="widefat striped"> $current_url_params = $_GET;
<thead> unset($current_url_params['orderby'], $current_url_params['order']); // Remove old sorting params
<tr> $current_url = http_build_query($current_url_params); // Build the remaining query string
<th><?php esc_html_e('ID', 'slm-plus'); ?></th>
<th><?php esc_html_e('Action', 'slm-plus'); ?></th> // Fetch sorting parameters with defaults
<th><?php esc_html_e('Time', 'slm-plus'); ?></th> $orderby = isset($_GET['orderby']) ? esc_sql($_GET['orderby']) : 'time';
<th><?php esc_html_e('Source', 'slm-plus'); ?></th> $order = isset($_GET['order']) && in_array(strtoupper($_GET['order']), ['ASC', 'DESC']) ? esc_sql($_GET['order']) : 'DESC';
</tr>
</thead> // Validate $orderby to ensure it's a valid column
<tbody> $valid_columns = ['id', 'slm_action', 'time', 'time_only', 'source'];
<?php foreach ($log_entries as $entry): ?> if (!in_array($orderby, $valid_columns)) {
<tr> $orderby = 'time'; // Fallback to default
<td><?php echo esc_html($entry['id']); ?></td> }
<td><?php echo esc_html($entry['slm_action']); ?></td>
<td><?php echo esc_html($entry['time']); ?></td> // Fetch data with sorting
<td><?php echo esc_html($entry['source']); ?></td> $query = $wpdb->prepare(
</tr> "SELECT * FROM $table_name WHERE license_key = %s ORDER BY $orderby $order LIMIT 10 OFFSET %d",
<?php endforeach; ?> esc_sql($data['license_key']),
</tbody> (isset($_GET['paged']) ? intval($_GET['paged'] - 1) * 10 : 0) // Calculate offset for pagination
</table> );
</div> $log_entries = $wpdb->get_results($query, ARRAY_A);
?>

<div class="wrap">
<h2><?php esc_html_e('Activity Log', 'slm-plus'); ?></h2>
<?php if (!empty($log_entries)): ?>
<table class="widefat striped">
<thead>
<tr>
<?php <?php
} else { // Define all columns to be displayed and sortable
// Show a message if there are no log entries $columns = [
echo '<p>' . esc_html__('No activity log found for this license.', 'slm-plus') . '</p>'; 'id' => __('ID', 'slm-plus'),
} 'slm_action' => __('Action', 'slm-plus'),
'time' => __('Date & Time', 'slm-plus'),
'source' => __('Source', 'slm-plus'),
];

// Render table headers with sorting links
foreach ($columns as $column_key => $column_name):
$next_order = ($orderby === $column_key && $order === 'ASC') ? 'DESC' : 'ASC';
?> ?>
<th>
<a href="?<?php echo $current_url; ?>&orderby=<?php echo esc_attr($column_key); ?>&order=<?php echo esc_attr($next_order); ?>">
<?php echo esc_html($column_name); ?>
<?php if ($orderby === $column_key): ?>
<?php echo $order === 'ASC' ? '&#9650;' : '&#9660;'; ?> <!-- Arrow for sorting -->
<?php endif; ?>
</a>
</th>
<?php endforeach; ?>
</tr>
</thead>
<tbody>
<?php foreach ($log_entries as $entry): ?>
<tr>
<td><?php echo esc_html($entry['id']); ?></td>
<td><?php echo esc_html($entry['slm_action']); ?></td>
<td><?php echo esc_html($entry['time']); ?></td>
<td><?php echo esc_html($entry['source']); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php else: ?>
<p><?php esc_html_e('No activity log found for this license.', 'slm-plus'); ?></p>
<?php endif; ?>
</div>





<?php <?php
break; break;

View file

@ -37,6 +37,48 @@ if (version_compare($used_db_version, $new_db_version, '<')) {
// Check if the 'associated_orders' column exists // Check if the 'associated_orders' column exists
$column_exists = $wpdb->get_results("SHOW COLUMNS FROM $lic_key_table LIKE 'associated_orders'"); $column_exists = $wpdb->get_results("SHOW COLUMNS FROM $lic_key_table LIKE 'associated_orders'");


// Ensure the 'time' column is DATETIME
$check_time_column = $wpdb->get_results("SHOW COLUMNS FROM $lic_log_tbl LIKE 'time'");
if (!empty($check_time_column)) {
$time_column_info = $wpdb->get_row("SHOW COLUMNS FROM $lic_log_tbl WHERE Field = 'time'");
if ($time_column_info->Type !== 'datetime') {
error_log("SLM: Updating 'time' column to DATETIME in $lic_log_tbl.");
$update_time_column_query = "
ALTER TABLE $lic_log_tbl
MODIFY COLUMN time DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00';
";
$update_time_column_result = $wpdb->query($update_time_column_query);
if ($update_time_column_result === false) {
error_log("SLM: Error updating 'time' column - " . $wpdb->last_error);
} else {
error_log("SLM: 'time' column updated successfully to DATETIME.");
}
} else {
error_log("SLM: 'time' column is already DATETIME.");
}
} else {
error_log("SLM: 'time' column does not exist in $lic_log_tbl. Skipping update.");
}

// Add the 'time_only' column if it doesn't exist
$check_time_only_column = $wpdb->get_results("SHOW COLUMNS FROM $lic_log_tbl LIKE 'time_only'");
if (empty($check_time_only_column)) {
error_log("SLM: Adding missing column 'time_only' to $lic_log_tbl.");
$add_time_only_column_query = "
ALTER TABLE $lic_log_tbl
ADD COLUMN time_only TIME NOT NULL DEFAULT '00:00:00';
";
$add_time_only_column_result = $wpdb->query($add_time_only_column_query);
if ($add_time_only_column_result === false) {
error_log("SLM: Error adding 'time_only' column - " . $wpdb->last_error);
} else {
error_log("SLM: 'time_only' column added successfully.");
}
} else {
error_log("SLM: Column 'time_only' already exists in $lic_log_tbl.");
}


if (empty($column_exists)) { if (empty($column_exists)) {
error_log("SLM: Adding missing column 'associated_orders' to $lic_key_table."); error_log("SLM: Adding missing column 'associated_orders' to $lic_key_table.");


@ -200,16 +242,18 @@ $slm_emails_tbl = "CREATE TABLE IF NOT EXISTS " . $lic_emails_table . " (
dbDelta($slm_emails_tbl); dbDelta($slm_emails_tbl);


// Create log table if not exists // Create log table if not exists
$log_tbl_sql = "CREATE TABLE IF NOT EXISTS " . $lic_log_tbl . " ( $log_tbl_sql = "CREATE TABLE IF NOT EXISTS" . $lic_log_tbl . " (
id INT NOT NULL AUTO_INCREMENT, id INT NOT NULL AUTO_INCREMENT,
license_key varchar(255) NOT NULL, license_key VARCHAR(255) NOT NULL,
slm_action varchar(255) NOT NULL, slm_action VARCHAR(255) NOT NULL,
time date NOT NULL DEFAULT '0000-00-00', time DATETIME NOT NULL DEFAULT '0000-00-00 00:00:00', -- Store combined date and time
source varchar(255) NOT NULL, time_only TIME NOT NULL DEFAULT '00:00:00', -- Store time only
PRIMARY KEY (id) source VARCHAR(255) NOT NULL,
)" . $charset_collate . ";"; PRIMARY KEY (id)
) $charset_collate;";
dbDelta($log_tbl_sql); dbDelta($log_tbl_sql);



// Create devices table if not exists // Create devices table if not exists
$ldv_tbl_sql = "CREATE TABLE IF NOT EXISTS " . $lic_devices_table . " ( $ldv_tbl_sql = "CREATE TABLE IF NOT EXISTS " . $lic_devices_table . " (
id INT NOT NULL AUTO_INCREMENT, id INT NOT NULL AUTO_INCREMENT,

View file

@ -140,7 +140,7 @@ function slmplus_init_handler()
function slmplus_plugins_loaded_handler() function slmplus_plugins_loaded_handler()
{ {
if (is_admin() && get_option('slm_db_version') != SLM_DB_VERSION) { if (is_admin() && get_option('slm_db_version') != SLM_DB_VERSION) {
require_once(SLM_LIB . 'class-slm-installer.php'); require_once SLM_LIB . 'class-slm-installer.php';
// TODO - Implement DB update logic here // TODO - Implement DB update logic here
} }
} }
@ -208,8 +208,8 @@ function wc_log($msg)


// WooCommerce integration // WooCommerce integration
if (SLM_Helper_Class::slm_get_option('slm_woo') == 1 && is_plugin_active('woocommerce/woocommerce.php')) { if (SLM_Helper_Class::slm_get_option('slm_woo') == 1 && is_plugin_active('woocommerce/woocommerce.php')) {
require_once(SLM_WOO . 'includes/wc_licenses_class.php'); require_once SLM_WOO . 'includes/wc_licenses_class.php';
require_once(SLM_WOO . 'includes/slm-meta-boxes.php'); require_once SLM_WOO . 'includes/slm-meta-boxes.php';
require_once SLM_WOO . 'includes/register-template.php'; require_once SLM_WOO . 'includes/register-template.php';
require_once SLM_WOO . 'includes/purchase.php'; require_once SLM_WOO . 'includes/purchase.php';
require_once SLM_WOO . 'includes/create-license-orders.php'; require_once SLM_WOO . 'includes/create-license-orders.php';

View file

@ -1182,36 +1182,47 @@ class SLM_Utility
{ {
global $wpdb; global $wpdb;
$slm_log_table = SLM_TBL_LIC_LOG; $slm_log_table = SLM_TBL_LIC_LOG;

// Sanitize inputs // Sanitize inputs
$license_key = sanitize_text_field($license_key); $license_key = sanitize_text_field($license_key);
$action = sanitize_text_field($action); $action = sanitize_text_field($action);

// Determine the request origin // Determine the request origin
$origin = '';
if (!empty($_SERVER['HTTP_ORIGIN'])) { if (!empty($_SERVER['HTTP_ORIGIN'])) {
$origin = sanitize_text_field($_SERVER['HTTP_ORIGIN']); $origin = sanitize_text_field($_SERVER['HTTP_ORIGIN']);
} elseif (!empty($_SERVER['HTTP_REFERER'])) { } elseif (!empty($_SERVER['HTTP_REFERER'])) {
$origin = sanitize_text_field($_SERVER['HTTP_REFERER']); $origin = sanitize_text_field($_SERVER['HTTP_REFERER']);
} else { } elseif (!empty($_SERVER['REMOTE_ADDR'])) {
$origin = sanitize_text_field($_SERVER['REMOTE_ADDR']); $origin = sanitize_text_field($_SERVER['REMOTE_ADDR']);
} }

// Get current date and time
$current_date_time = current_time('mysql'); // Returns 'Y-m-d H:i:s' in WordPress timezone
$current_time_only = date('H:i:s', strtotime($current_date_time)); // Extract time portion
// Prepare log data // Prepare log data
$log_data = array( $log_data = [
'license_key' => $license_key, 'license_key' => $license_key,
'slm_action' => $action, 'slm_action' => $action,
'time' => current_time('mysql'), // Standardized date-time format 'time' => $current_date_time, // Combined date and time
'time_only' => $current_time_only, // Time only
'source' => $origin, 'source' => $origin,
); ];

// Insert log data into the database // Insert log data into the database
$inserted = $wpdb->insert($slm_log_table, $log_data); $inserted = $wpdb->insert($slm_log_table, $log_data);

// Check for insertion errors // Check for insertion errors
if ($inserted === false) { if ($inserted === false) {
error_log("Failed to insert log for license key: $license_key, action: $action. Error: " . $wpdb->last_error); error_log("Failed to insert log for license key: $license_key, action: $action. Error: " . $wpdb->last_error);
} else {
error_log("Log inserted successfully for license key: $license_key, action: $action.");
} }
} }




public static function create_email_log($lic_key, $sent_to, $status, $sent, $date_sent = null) public static function create_email_log($lic_key, $sent_to, $status, $sent, $date_sent = null)
@ -1420,3 +1431,4 @@ class SLM_Utility
} }
} }
} }


View file

@ -1,7 +1,7 @@
<?php <?php
/* /*
Plugin Name: SLM Plus Plugin Name: SLM Plus
Version: 6.3.5 Version: 6.3.6
Plugin URI: https://github.com/michelve/software-license-manager/ Plugin URI: https://github.com/michelve/software-license-manager/
Author: Michel Velis Author: Michel Velis
Author URI: https://github.com/michelve/ Author URI: https://github.com/michelve/
@ -23,8 +23,7 @@ if (!defined('ABSPATH')) {
} }


// Load plugin textdomain for multilingual support // Load plugin textdomain for multilingual support
function slmplus_load_textdomain() function slmplus_load_textdomain() {
{
load_plugin_textdomain('slm-plus', false, dirname(plugin_basename(__FILE__)) . '/i18n/languages'); load_plugin_textdomain('slm-plus', false, dirname(plugin_basename(__FILE__)) . '/i18n/languages');
} }
add_action('plugins_loaded', 'slmplus_load_textdomain'); add_action('plugins_loaded', 'slmplus_load_textdomain');
@ -33,53 +32,57 @@ add_action('plugins_loaded', 'slmplus_load_textdomain');
global $wpdb, $slm_debug_logger; global $wpdb, $slm_debug_logger;


// Define constants for plugin paths, URLs, and database tables // Define constants for plugin paths, URLs, and database tables
define('SLM_VERSION', '6.3.5'); define('SLM_VERSION', '6.3.6');
define('SLM_DB_VERSION', '5.8.10'); define('SLM_DB_VERSION', '5.9.1');
define('SLM_REWRITE_VERSION', '3.1.3'); define('SLM_REWRITE_VERSION', '3.1.3');
define('SLM_FOLDER', dirname(plugin_basename(__FILE__)));
define('SLM_URL', plugins_url('', __FILE__)); define('SLM_FOLDER', dirname(plugin_basename(__FILE__)));
define('SLM_ASSETS_URL', SLM_URL . '/public/assets/'); define('SLM_URL', plugins_url('', __FILE__));
define('SLM_PATH', plugin_dir_path(__FILE__)); define('SLM_ASSETS_URL', SLM_URL . '/public/assets/');
define('SLM_LIB', SLM_PATH . 'includes/'); define('SLM_PATH', plugin_dir_path(__FILE__));
define('SLM_WOO', SLM_PATH . 'woocommerce/'); define('SLM_LIB', SLM_PATH . 'includes/');
define('SLM_ADDONS', SLM_PATH . 'addons/'); define('SLM_WOO', SLM_PATH . 'woocommerce/');
define('SLM_ADMIN', SLM_PATH . 'admin/'); define('SLM_ADDONS', SLM_PATH . 'addons/');
define('SLM_ADMIN_ADDONS', SLM_ADMIN . 'includes/'); define('SLM_ADMIN', SLM_PATH . 'admin/');
define('SLM_CRONS', SLM_ADMIN_ADDONS . 'cronjobs/'); define('SLM_ADMIN_ADDONS', SLM_ADMIN . 'includes/');
define('SLM_PUBLIC', SLM_PATH . 'public/'); define('SLM_CRONS', SLM_ADMIN_ADDONS . 'cronjobs/');
define('SLM_TEMPLATES', SLM_PATH . 'templates/'); define('SLM_PUBLIC', SLM_PATH . 'public/');
define('SLM_SITE_HOME_URL', get_home_url()); define('SLM_TEMPLATES', SLM_PATH . 'templates/');
define('SLM_SITE_URL', get_site_url() . '/');
define('SLM_TBL_LICENSE_KEYS', $wpdb->prefix . "lic_key_tbl"); define('SLM_SITE_HOME_URL', get_home_url());
define('SLM_TBL_EMAILS', $wpdb->prefix . "lic_emails_tbl"); define('SLM_SITE_URL', get_site_url() . '/');
define('SLM_TBL_LIC_DOMAIN', $wpdb->prefix . "lic_reg_domain_tbl");
define('SLM_TBL_LIC_DEVICES', $wpdb->prefix . "lic_reg_devices_tbl"); define('SLM_TBL_LICENSE_KEYS', $wpdb->prefix . "lic_key_tbl");
define('SLM_TBL_LIC_LOG', $wpdb->prefix . "lic_log_tbl"); define('SLM_TBL_EMAILS', $wpdb->prefix . "lic_emails_tbl");
define('SLM_TBL_LICENSE_STATUS', $wpdb->prefix . "lic_status_tbl"); define('SLM_TBL_LIC_DOMAIN', $wpdb->prefix . "lic_reg_domain_tbl");
define('SLM_TBL_LIC_DEVICES', $wpdb->prefix . "lic_reg_devices_tbl");
define('SLM_TBL_LIC_LOG', $wpdb->prefix . "lic_log_tbl");
define('SLM_TBL_LICENSE_STATUS', $wpdb->prefix . "lic_status_tbl");

define('SLM_MANAGEMENT_PERMISSION', 'manage_options'); define('SLM_MANAGEMENT_PERMISSION', 'manage_options');
define('SLM_MAIN_MENU_SLUG', 'slm_overview'); define('SLM_MAIN_MENU_SLUG', 'slm_overview');
define('SLM_MENU_ICON', 'dashicons-lock'); define('SLM_MENU_ICON', 'dashicons-lock');
define('SLM_API_URL', SLM_SITE_URL); define('SLM_API_URL', SLM_SITE_URL);


// Load core plugin functionalities // Load core plugin functionalities
if (file_exists(SLM_LIB . 'slm-plugin-core.php')) { if (file_exists(SLM_LIB . 'slm-plugin-core.php')) {
require_once SLM_LIB . 'slm-plugin-core.php'; require_once SLM_LIB . 'slm-plugin-core.php';
} }


function slm_settings_link($links) // Add settings link to plugin action links
{ function slm_settings_link($links) {
$settings_link = '<a href="' . esc_url(admin_url('admin.php?page=slm_settings')) . '">' . __('Settings', 'slm-plus') . '</a>'; $settings_link = '<a href="' . esc_url(admin_url('admin.php?page=slm_settings')) . '">' . __('Settings', 'slm-plus') . '</a>';
$links[] = $settings_link; $links[] = $settings_link;
return $links; return $links;
} }
add_filter('plugin_action_links_' . plugin_basename(__FILE__), 'slm_settings_link'); add_filter('plugin_action_links_' . plugin_basename(__FILE__), 'slm_settings_link');



// Define default max domains and devices
define('SLM_DEFAULT_MAX_DOMAINS', SLM_API_Utility::get_slm_option('default_max_domains')); define('SLM_DEFAULT_MAX_DOMAINS', SLM_API_Utility::get_slm_option('default_max_domains'));
define('SLM_DEFAULT_MAX_DEVICES', SLM_API_Utility::get_slm_option('default_max_devices')); define('SLM_DEFAULT_MAX_DEVICES', SLM_API_Utility::get_slm_option('default_max_devices'));


// Use native WordPress function for setting options // Use native WordPress function for setting options
define('WOO_SLM_API_SECRET', SLM_API_Utility::get_slm_option('lic_creation_secret')); define('WOO_SLM_API_SECRET', SLM_API_Utility::get_slm_option('lic_creation_secret'));
define('KEY_API', SLM_API_Utility::get_slm_option('lic_creation_secret')); define('KEY_API', SLM_API_Utility::get_slm_option('lic_creation_secret'));
define('VERIFY_KEY_API', SLM_API_Utility::get_slm_option('lic_verification_secret')); define('VERIFY_KEY_API', SLM_API_Utility::get_slm_option('lic_verification_secret'));
define('KEY_API_PREFIX', SLM_API_Utility::get_slm_option('lic_prefix')); define('KEY_API_PREFIX', SLM_API_Utility::get_slm_option('lic_prefix'));

View file

@ -50,7 +50,7 @@ foreach ($tables_to_drop as $table) {
// Check if the table exists before attempting to drop it // Check if the table exists before attempting to drop it
if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table)) !== null) { if ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $table)) !== null) {
// Drop the table if it exists // Drop the table if it exists
$wpdb->query($wpdb->prepare("DROP TABLE IF EXISTS %s", $table)); $wpdb->query("DROP TABLE IF EXISTS {$table}");
} }
} }


@ -70,20 +70,16 @@ foreach ($post_types as $post_type) {


// Clean orphaned postmeta entries using `DELETE` queries // Clean orphaned postmeta entries using `DELETE` queries
$wpdb->query( $wpdb->query(
$wpdb->prepare( "DELETE pm FROM {$wpdb->postmeta} pm
"DELETE pm FROM {$wpdb->postmeta} pm LEFT JOIN {$wpdb->posts} p ON pm.post_id = p.ID
LEFT JOIN {$wpdb->posts} p ON pm.post_id = p.ID WHERE p.ID IS NULL"
WHERE p.ID IS NULL"
)
); );


// Clean orphaned term relationships if there are custom taxonomies involved // Clean orphaned term relationships if there are custom taxonomies involved
$wpdb->query( $wpdb->query(
$wpdb->prepare( "DELETE tr FROM {$wpdb->term_relationships} tr
"DELETE tr FROM {$wpdb->term_relationships} tr LEFT JOIN {$wpdb->posts} p ON tr.object_id = p.ID
LEFT JOIN {$wpdb->posts} p ON tr.object_id = p.ID WHERE p.ID IS NULL"
WHERE p.ID IS NULL"
)
); );


// Delete custom user meta related to the plugin (if applicable) // Delete custom user meta related to the plugin (if applicable)