';
}
}
/**
* Display the settings page.
*
* @since 1.0.0
*/
public function display_settings_page() {
// Check user capabilities
if (!current_user_can('manage_options')) {
return;
}
// Display the logo before including the template
$this->display_plugin_logo();
// Display horizontal tabs if menu is under Dashboard or Tools
$this->display_horizontal_tabs();
// Add page title if horizontal tabs are active
$menu_location = get_option('actlogpro_admin_menu_location', 'default');
if (in_array($menu_location, array('dashboard', 'tools'))) {
echo '
';
echo '';
}
// Include the settings template
include_once ACTLOGPRO_PLUGIN_DIR . 'admin/partials/settings-display.php';
// Close wrap if opened
if (in_array($menu_location, array('dashboard', 'tools'))) {
echo '
';
}
}
/**
* AJAX handler for getting activity logs.
*
* @since 1.0.0
*/
public function ajax_get_activity_logs() {
// Check nonce
if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_key(wp_unslash($_POST['nonce'])), 'actlogpro_ajax_nonce')) {
wp_send_json_error(array('message' => __('Security check failed.', 'activity-log-pro')));
}
// Check user capabilities
if (!current_user_can('manage_options')) {
wp_send_json_error(array('message' => __('You do not have permission to perform this action.', 'activity-log-pro')));
}
// Get saved items per page setting
$saved_per_page = get_option('actlogpro_items_per_page', 20);
// Get parameters
$page = isset($_POST['page']) ? absint(wp_unslash($_POST['page'])) : 1;
$per_page = isset($_POST['per_page']) ? absint(wp_unslash($_POST['per_page'])) : $saved_per_page;
$date_from = isset($_POST['date_from']) ? sanitize_text_field(wp_unslash($_POST['date_from'])) : '';
$date_to = isset($_POST['date_to']) ? sanitize_text_field(wp_unslash($_POST['date_to'])) : '';
$user = isset($_POST['user']) ? sanitize_text_field(wp_unslash($_POST['user'])) : '';
$event_type = isset($_POST['event_type']) ? sanitize_text_field(wp_unslash($_POST['event_type'])) : '';
$action_filter = isset($_POST['action_filter']) ? sanitize_text_field(wp_unslash($_POST['action_filter'])) : '';
$ip_address = isset($_POST['ip_address']) ? sanitize_text_field(wp_unslash($_POST['ip_address'])) : '';
$search = isset($_POST['search']) ? sanitize_text_field(wp_unslash($_POST['search'])) : '';
$severity = isset($_POST['severity']) ? sanitize_text_field(wp_unslash($_POST['severity'])) : '';
$filter_preset = isset($_POST['filter_preset']) ? sanitize_key(wp_unslash($_POST['filter_preset'])) : '';
$allowed_presets = array( 'failed_logins', 'critical_high' );
if ( ! in_array( $filter_preset, $allowed_presets, true ) ) {
$filter_preset = '';
}
// Validate severity against allowed values to prevent DB injection
if ( ! empty($severity) && ! ACTLOGPRO_Severity::is_valid($severity) ) {
$severity = '';
}
// Get sort parameters
$sort_column = isset($_POST['sort_column']) ? sanitize_sql_orderby(wp_unslash($_POST['sort_column'])) : 'created_at';
$sort_direction = isset($_POST['sort_direction']) && sanitize_text_field(wp_unslash($_POST['sort_direction'])) === 'asc' ? 'ASC' : 'DESC';
// Validate sort column
$allowed_columns = array('id', 'severity', 'user_login', 'user_role', 'ip_address', 'event_type', 'object_type', 'object_name', 'action', 'created_at');
if (!in_array($sort_column, $allowed_columns)) {
$sort_column = 'created_at';
}
// Prepare query args for database abstraction layer
$query_args = array(
'page' => $page,
'per_page' => $per_page,
'orderby' => $sort_column,
'order' => $sort_direction,
'user' => $user,
'event_type' => $event_type,
'action' => $action_filter,
'ip_address' => $ip_address,
'date_from' => $date_from,
'date_to' => $date_to,
'search' => $search,
'severity' => $severity,
'filter_preset' => $filter_preset,
);
// Load database class if not already loaded
if (!class_exists('ACTLOGPRO_Database')) {
require_once ACTLOGPRO_PLUGIN_DIR . 'includes/class-alp-database.php';
}
// Get total count using the database abstraction layer
$total = ACTLOGPRO_Database::get_filtered_logs_count($query_args);
// Get logs using the database abstraction layer
$logs = ACTLOGPRO_Database::get_filtered_logs($query_args);
// Format logs for display
$formatted_logs = array();
foreach ($logs as $log) {
$description = $log->description;
// Modify description for plugin events to use the new format
if ($log->event_type === 'plugin') {
$metadata = ACTLOGPRO_Database::decode_log_metadata( $log->metadata );
// Check if it's an activation event
if ($log->action === 'activate') {
// Always reformat the description for plugin activation events
if (strpos($description, 'Plugin Activated:') === false) {
// Extract plugin name from either format
if (preg_match('/Plugin "(.+?)" activated/', $description, $matches)) {
$plugin_name = $matches[1];
$description = 'Plugin Activated: ' . $plugin_name;
} else {
$plugin_name = $log->object_name;
$description = 'Plugin Activated: ' . $plugin_name;
}
}
}
// Check if it's a deactivation event
if ($log->action === 'deactivate') {
// Extract plugin name from the description
if (preg_match('/Plugin "(.+?)" deactivated/', $description, $matches)) {
$plugin_name = $matches[1];
$description = 'Plugin Deactivated: ' . $plugin_name;
}
}
// Check if it's an update event
if ($log->action === 'update') {
// Extract plugin name and version from the description
if (preg_match('/Plugin "(.+?)" was updated to version (.+?)/', $description, $matches)) {
$plugin_name = $matches[1];
$version = $matches[2];
$description = 'Plugin Updated: ' . $plugin_name . ' to version ' . $version;
}
}
// Clear any existing version/location info from the description
$description = preg_replace('/ Plugin Version:<\/strong>.*?( |$)/i', '', $description);
$description = preg_replace('/ Install location:<\/strong>.*?( |$)/i', '', $description);
// Add plugin version if available
if (!empty($metadata['plugin_version'])) {
$description .= ' Plugin Version: ' . esc_html($metadata['plugin_version']);
}
// Add plugin path if available - check both possible keys and format to show only directory
if (!empty($metadata['plugin_path'])) {
$plugin_dir = dirname($metadata['plugin_path']) . '/';
$description .= ' Install location: ' . esc_html($plugin_dir);
} elseif (!empty($metadata['plugin_file'])) {
// Use the plugin_file directly without prepending WP_PLUGIN_DIR
$plugin_dir = dirname($metadata['plugin_file']) . '/';
$description .= ' Install location: ' . esc_html($plugin_dir);
} else {
$description .= ' Install location: Not available';
}
}
$log_severity = ( isset( $log->severity ) && ACTLOGPRO_Severity::is_valid( $log->severity ) ) ? $log->severity : ACTLOGPRO_Severity::LEVEL_DEFAULT;
$formatted_logs[] = array(
'id' => $log->id,
'severity' => $log_severity,
'severity_label' => ACTLOGPRO_Severity::get_label( $log_severity ),
'severity_class' => ACTLOGPRO_Severity::get_css_class( $log_severity ),
'user' => array(
'id' => $log->user_id,
'login' => $log->user_login,
'display_name' => $log->user_display_name,
'role' => $log->user_role
),
'ip_address' => $log->ip_address,
'event_type' => $log->event_type,
'object' => array(
'type' => $log->object_type,
'id' => $log->object_id,
'name' => $log->object_name
),
'action' => $log->action,
'description' => $description,
'metadata' => ACTLOGPRO_Database::decode_log_metadata( $log->metadata ),
'created_at' => $log->created_at
);
}
// Calculate total pages
$total_pages = ceil($total / $per_page);
// Send response
wp_send_json_success(array(
'logs' => $formatted_logs,
'total' => $total,
'total_pages' => $total_pages,
'current_page' => $page
));
}
/**
* AJAX handler for clearing all logs.
*
* Security: Requires 'manage_options' capability and valid nonce verification.
* This function permanently deletes all activity log entries from the database.
*
* @since 1.0.0
*/
public function ajax_clear_logs() {
// Check nonce
if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_key(wp_unslash($_POST['nonce'])), 'actlogpro_ajax_nonce')) {
wp_send_json_error(array('message' => __('Security check failed.', 'activity-log-pro')));
}
// Check user capabilities
if (!current_user_can('manage_options')) {
wp_send_json_error(array('message' => __('You do not have permission to perform this action.', 'activity-log-pro')));
}
// Load database class
require_once ACTLOGPRO_PLUGIN_DIR . 'includes/class-alp-database.php';
// Clear logs using the database abstraction class
$result = ACTLOGPRO_Database::clear_all_logs();
if ($result !== false) {
// Clear all caches related to activity logs
if (function_exists('wp_cache_flush_group')) {
wp_cache_flush_group('activity-log-pro');
} else {
// For older WordPress versions without group flushing
// Set a flag to indicate that the cache needs refreshing
update_option('actlogpro_cache_invalidated', time());
}
wp_send_json_success(array('message' => __('All logs have been cleared successfully.', 'activity-log-pro')));
} else {
wp_send_json_error(array('message' => __('An error occurred while clearing logs.', 'activity-log-pro')));
}
}
/**
* AJAX handler for deleting selected logs.
*
* Security: Requires 'manage_options' capability and valid nonce verification.
* This function permanently deletes specific activity log entries from the database.
*
* @since 1.0.0
*/
public function ajax_delete_selected_logs() {
// Check nonce
if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_key(wp_unslash($_POST['nonce'])), 'actlogpro_ajax_nonce')) {
wp_send_json_error(array('message' => __('Security check failed.', 'activity-log-pro')));
}
// Check user capabilities
if (!current_user_can('manage_options')) {
wp_send_json_error(array('message' => __('You do not have permission to perform this action.', 'activity-log-pro')));
}
// Check if log_ids exists in $_POST and sanitize
if (!isset($_POST['log_ids'])) {
$log_ids = array();
} else {
$raw_log_ids = map_deep(wp_unslash($_POST['log_ids']), 'sanitize_text_field');
$log_ids = array();
if (is_array($raw_log_ids)) {
foreach ($raw_log_ids as $id) {
$log_ids[] = absint($id);
}
} else {
$log_ids[] = absint($raw_log_ids);
}
}
if (empty($log_ids) || !is_array($log_ids)) {
wp_send_json_error(array('message' => __('No logs selected for deletion.', 'activity-log-pro')));
}
// Load database class if not already loaded
if (!class_exists('ACTLOGPRO_Database')) {
require_once ACTLOGPRO_PLUGIN_DIR . 'includes/class-alp-database.php';
}
// Delete logs using the database abstraction layer
$result = ACTLOGPRO_Database::delete_logs_by_ids($log_ids);
// If logs were deleted successfully
if ($result !== false) {
wp_send_json_success(array(
/* translators: %d: Number of logs deleted */
'message' => sprintf(__('%d logs have been deleted successfully.', 'activity-log-pro'), $result),
'deleted_count' => $result
));
} else {
wp_send_json_error(array('message' => __('An error occurred while deleting logs.', 'activity-log-pro')));
}
}
/**
* AJAX handler for recording a manual note into the activity log.
*
* Security: Requires 'manage_options' capability and valid nonce verification.
* Premium fields (backdate, attribution) are silently ignored when premium is not active.
*
* @since 1.2.0
*/
public function ajax_record_note() {
// Verify nonce.
if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_key( wp_unslash( $_POST['nonce'] ) ), 'actlogpro_ajax_nonce' ) ) {
wp_send_json_error( array( 'message' => __( 'Security check failed.', 'activity-log-pro' ) ) );
}
// Check capability — only administrators may record notes.
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => __( 'You do not have permission to record notes.', 'activity-log-pro' ) ) );
}
// Validate and sanitize required message field.
$message = isset( $_POST['message'] ) ? sanitize_text_field( wp_unslash( $_POST['message'] ) ) : '';
if ( empty( $message ) ) {
wp_send_json_error( array( 'message' => __( 'A note message is required.', 'activity-log-pro' ) ) );
}
// Sanitize optional free fields.
$details = isset( $_POST['details'] ) ? sanitize_textarea_field( wp_unslash( $_POST['details'] ) ) : '';
$severity = isset( $_POST['severity'] ) ? sanitize_text_field( wp_unslash( $_POST['severity'] ) ) : 'info';
// Premium-only fields — ignored entirely when premium is not active.
$event_datetime = null;
$attributed_user_id = null;
if ( ACTLOGPRO_Premium_Helper::is_premium_enabled() ) {
// Backdate: accept YYYY-MM-DD HH:MM, disallow future timestamps.
if ( ! empty( $_POST['event_datetime'] ) ) {
$raw_dt = sanitize_text_field( wp_unslash( $_POST['event_datetime'] ) );
if ( preg_match( '/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/', $raw_dt ) ) {
$submitted_ts = strtotime( $raw_dt );
if ( $submitted_ts && $submitted_ts <= current_time( 'timestamp' ) ) {
$event_datetime = $raw_dt . ':00'; // Normalise to full MySQL datetime.
}
}
}
// Attribution: validate user exists and is not a subscriber.
if ( ! empty( $_POST['attributed_user_id'] ) ) {
$uid = absint( wp_unslash( $_POST['attributed_user_id'] ) );
$attr_user = $uid > 0 ? get_userdata( $uid ) : false;
if ( $attr_user && $attr_user->ID && ! in_array( 'subscriber', (array) $attr_user->roles, true ) ) {
$attributed_user_id = $uid;
}
}
}
// Load and call the recorded notes class.
if ( ! class_exists( 'ACTLOGPRO_Recorded_Notes' ) ) {
require_once ACTLOGPRO_PLUGIN_DIR . 'includes/class-alp-recorded-notes.php';
}
$log_id = ACTLOGPRO_Recorded_Notes::record_note( $message, $details, $severity, $event_datetime, $attributed_user_id );
if ( $log_id ) {
wp_send_json_success( array(
'message' => __( 'Note recorded successfully.', 'activity-log-pro' ),
'log_id' => $log_id,
) );
} else {
wp_send_json_error( array( 'message' => __( 'Failed to record the note. Please try again.', 'activity-log-pro' ) ) );
}
}
/**
* Clean up old logs based on retention settings.
*
* @since 1.0.0
*/
public function cleanup_old_logs() {
$retention_days = get_option('actlogpro_retention_period', 30);
// If retention is set to 0 (forever), don't delete anything
if ($retention_days <= 0) {
return;
}
$deleted = ACTLOGPRO_Database::delete_old_logs($retention_days);
if ($deleted) {
// Log this action
ACTLOGPRO_Logger::log_activity(
'system',
'logs',
0,
'actlogpro_activity_log_pro_all_logs',
'cleanup',
/* translators: %1$d: Number of logs deleted, %2$d: Number of days in retention period */
sprintf(__('Deleted %1$d logs older than %2$d days', 'activity-log-pro'), $deleted, $retention_days)
);
}
}
/**
* Get log details for a specific log entry
*/
public function get_log_details() {
// Check nonce
if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_key(wp_unslash($_POST['nonce'])), 'actlogpro_ajax_nonce')) {
wp_send_json_error(['message' => __('Security check failed.', 'activity-log-pro')]);
}
// Check permissions
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => __('You do not have permission to perform this action.', 'activity-log-pro')]);
}
// Get log ID
$log_id = isset($_POST['log_id']) ? absint(wp_unslash($_POST['log_id'])) : 0;
if (!$log_id) {
wp_send_json_error(['message' => __('Invalid log ID.', 'activity-log-pro')]);
}
// Ensure the database class is loaded
require_once ACTLOGPRO_PLUGIN_DIR . 'includes/class-alp-database.php';
// Check if log is in cache
$log_cache_key = 'actlogpro_log_' . $log_id;
$log = wp_cache_get($log_cache_key, 'activity-log-pro');
if (false === $log) {
// Not in cache, get from database using our abstraction method
$log = ACTLOGPRO_Database::get_log_by_id($log_id);
// Cache the result for 1 hour
if ($log) {
wp_cache_set($log_cache_key, $log, 'activity-log-pro', HOUR_IN_SECONDS);
}
}
if (!$log) {
wp_send_json_error(['message' => __('Log entry not found.', 'activity-log-pro')]);
}
// Format the log data
$formatted_log = $this->format_log_for_display($log);
// Add context data if available
if (!empty($log['metadata'])) {
$formatted_log['context'] = ACTLOGPRO_Database::decode_log_metadata( $log['metadata'] );
} else {
$formatted_log['context'] = [];
}
wp_send_json_success($formatted_log);
}
/**
* Format log data for display
*/
private function format_log_for_display($log) {
// Format user data
$user_data = [];
if (!empty($log['user_id']) && $log['user_id'] != 0) {
// Regular user - get data from WordPress user system
$user = get_userdata($log['user_id']);
if ($user) {
$user_data = [
'id' => $user->ID,
'login' => $user->user_login,
'display_name' => $user->display_name,
'role' => !empty($user->roles) ? reset($user->roles) : ''
];
}
} elseif ($log['user_id'] == 0 || !empty($log['user_login'])) {
// System user or user with stored login data - use stored data from log
$user_data = [
'id' => $log['user_id'],
'login' => $log['user_login'] ?? 'system',
'display_name' => $log['user_display_name'] ?? 'System',
'role' => 'Automated Process'
];
}
// Format object data
$object_data = [];
if (!empty($log['object_type'])) {
$object_data = [
'type' => $log['object_type'],
'name' => $log['object_name']
];
}
// Get metadata
$metadata = ACTLOGPRO_Database::decode_log_metadata( $log['metadata'] );
$severity = $log['severity'] ?? ACTLOGPRO_Severity::LEVEL_DEFAULT;
return [
'id' => $log['id'],
'severity' => ACTLOGPRO_Severity::is_valid($severity) ? $severity : ACTLOGPRO_Severity::LEVEL_DEFAULT,
'severity_label' => ACTLOGPRO_Severity::get_label($severity),
'severity_class' => ACTLOGPRO_Severity::get_css_class($severity),
'created_at' => $log['created_at'],
'user_id' => $log['user_id'],
'user' => $user_data,
'ip_address' => $log['ip_address'],
'event_type' => $log['event_type'],
'object' => $object_data,
'action' => $log['action'],
'description' => $log['description'],
'metadata' => $metadata
];
}
/**
* AJAX handler for saving settings
*
* Security: Requires 'manage_options' capability and valid nonce verification.
* This function processes and updates all plugin settings via AJAX.
*
* @since 1.0.0
*/
public function save_settings() {
// Check nonce
if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_key(wp_unslash($_POST['nonce'])), 'actlogpro_ajax_nonce')) {
wp_send_json_error(__('Security check failed.', 'activity-log-pro'));
return;
}
// Check user capabilities
if (!current_user_can('manage_options')) {
wp_send_json_error(__('You do not have permission to save settings.', 'activity-log-pro'));
return;
}
// Save each setting
$settings = array(
'actlogpro_retention_period',
'actlogpro_items_per_page',
'actlogpro_track_user_events',
'actlogpro_track_post_events',
'actlogpro_track_media_events',
'actlogpro_track_comment_events',
'actlogpro_track_plugin_events',
'actlogpro_track_theme_events',
'actlogpro_track_widget_events',
'actlogpro_track_menu_events',
'actlogpro_track_option_events',
'actlogpro_track_wordpress_events',
'actlogpro_track_security_events',
'actlogpro_track_site_health',
'actlogpro_track_email_events',
'actlogpro_track_email_exclude_own',
'actlogpro_flood_guard_enabled',
'actlogpro_flood_guard_limit',
'actlogpro_enable_json_feed',
'actlogpro_enable_rss_feed',
'actlogpro_allow_multiple_expanded_rows',
'actlogpro_login_notification_enabled',
'actlogpro_login_notification_text',
'actlogpro_admin_menu_location',
'actlogpro_store_full_ip',
'actlogpro_remove_data_on_uninstall',
'actlogpro_excluded_option_names',
// Email digest settings
'actlogpro_email_digest_enabled',
'actlogpro_email_digest_frequency',
'actlogpro_email_digest_day',
'actlogpro_email_digest_time',
'actlogpro_email_digest_recipients'
);
// Check if admin menu location was changed
$old_menu_location = get_option('actlogpro_admin_menu_location', 'default');
$new_menu_location = isset($_POST['actlogpro_admin_menu_location']) ? sanitize_text_field(wp_unslash($_POST['actlogpro_admin_menu_location'])) : 'default';
$menu_location_changed = ($old_menu_location !== $new_menu_location);
$updated_settings = array();
foreach ($settings as $setting) {
// Login Flood Guard fields are disabled (omitted from POST) when Security events is off — keep stored values.
if ('actlogpro_flood_guard_enabled' === $setting || 'actlogpro_flood_guard_limit' === $setting) {
if (!isset($_POST['actlogpro_track_security_events'])) {
continue;
}
}
// Only process if the setting exists in POST data
if (isset($_POST[$setting])) {
// Apply both unslash and sanitization at access point
// The specific sanitization depends on the type of setting
$clean_value = ''; // Initialize clean value
// Get raw value from POST
// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized, WordPress.Security.ValidatedSanitizedInput.MissingUnslash -- Unslashed and sanitized below based on setting type
$raw_value = $_POST[$setting];
// Use wp_kses_post for login notification text to preserve HTML
if ($setting === 'actlogpro_login_notification_text') {
$clean_value = wp_kses_post(wp_unslash($raw_value));
} elseif ($setting === 'actlogpro_flood_guard_limit') {
$clean_value = (string) $this->sanitize_flood_guard_limit(wp_unslash($raw_value));
// Use specialized sanitization for email digest settings
} elseif ($setting === 'actlogpro_email_digest_enabled') {
$clean_value = ACTLOGPRO_Email_Digest_Manager::sanitize_checkbox(wp_unslash($raw_value));
} elseif ($setting === 'actlogpro_email_digest_frequency') {
$clean_value = ACTLOGPRO_Email_Digest_Manager::sanitize_frequency(wp_unslash($raw_value));
} elseif ($setting === 'actlogpro_email_digest_day') {
$clean_value = ACTLOGPRO_Email_Digest_Manager::sanitize_day(wp_unslash($raw_value));
} elseif ($setting === 'actlogpro_email_digest_time') {
$clean_value = ACTLOGPRO_Email_Digest_Manager::sanitize_time(wp_unslash($raw_value));
} elseif ($setting === 'actlogpro_email_digest_recipients') {
$clean_value = ACTLOGPRO_Email_Digest_Manager::sanitize_email_recipients(wp_unslash($raw_value));
} else {
$clean_value = sanitize_text_field(wp_unslash($raw_value));
}
// Premium validation for IP address setting
if ($setting === 'actlogpro_store_full_ip') {
if (!ACTLOGPRO_Premium_Helper::is_premium_enabled() && $clean_value === '1') {
// Force to hide IP if not premium and trying to store full IP
$clean_value = '0';
$updated_settings['premium_blocked'] = 'actlogpro_store_full_ip';
}
}
// Update option with sanitized value
update_option($setting, $clean_value);
$updated_settings[$setting] = $clean_value;
} else {
// For checkboxes and radio buttons, if not set, save as 0
// Radio buttons like actlogpro_admin_menu_location should not be reset to 0
if (strpos($setting, 'track_') !== false || strpos($setting, 'enable_') !== false || $setting === 'actlogpro_store_full_ip' || $setting === 'actlogpro_allow_multiple_expanded_rows' || $setting === 'actlogpro_email_digest_enabled' || $setting === 'actlogpro_flood_guard_enabled') {
update_option($setting, '0');
$updated_settings[$setting] = '0';
}
}
}
// Refresh tracking settings cache
require_once ACTLOGPRO_PLUGIN_DIR . 'includes/class-alp-events.php';
$events = new ACTLOGPRO_Events();
$events->refresh_tracking_settings();
// Check if email digest settings changed and reschedule if needed
$email_digest_settings_changed = false;
$email_digest_settings = array(
'actlogpro_email_digest_enabled',
'actlogpro_email_digest_frequency',
'actlogpro_email_digest_day',
'actlogpro_email_digest_time'
);
foreach ($email_digest_settings as $digest_setting) {
if (isset($updated_settings[$digest_setting])) {
$email_digest_settings_changed = true;
break;
}
}
// Reschedule email digest if any email digest settings changed
if ($email_digest_settings_changed) {
ACTLOGPRO_Email_Digest_Manager::reschedule_email_digest();
}
// Save excluded roles and users (checkbox array + textarea, not in $settings loop).
$exclusion_warnings = $this->save_user_exclusion_settings();
// Prepare response message
$message = __('Settings saved successfully.', 'activity-log-pro');
if ($menu_location_changed) {
$message = __('Settings saved successfully. Please refresh the page to see the admin menu in its new location.', 'activity-log-pro');
}
// Return success with the updated settings
wp_send_json_success(array(
'message' => $message,
'menu_location_changed' => $menu_location_changed,
'settings' => $updated_settings,
'exclusion_warnings' => $exclusion_warnings,
));
}
/**
* Display the export page.
*
* @since 1.0.0
*/
public function display_export_page() {
// Check user capabilities
if (!current_user_can('manage_options')) {
return;
}
// Display the logo before including the template
$this->display_plugin_logo();
// Display horizontal tabs if menu is under Dashboard or Tools
$this->display_horizontal_tabs();
// Add page title if horizontal tabs are active
$menu_location = get_option('actlogpro_admin_menu_location', 'default');
if (in_array($menu_location, array('dashboard', 'tools'))) {
echo '
';
echo '';
}
// Include the export template
include_once ACTLOGPRO_PLUGIN_DIR . 'admin/partials/export-display.php';
// Close wrap if opened
if (in_array($menu_location, array('dashboard', 'tools'))) {
echo '
';
}
}
/**
* Display the integrations page.
*
* @since 1.0.0
*/
public function display_integrations_page() {
// Check user capabilities
if (!current_user_can('manage_options')) {
return;
}
// Make sure the integration manager class is loaded
if (!class_exists('ACTLOGPRO_Integration_Manager')) {
require_once ACTLOGPRO_PLUGIN_DIR . 'integrations/class-alp-integration-manager.php';
}
// Display the logo before including the template
$this->display_plugin_logo();
// Display horizontal tabs if menu is under Dashboard or Tools
$this->display_horizontal_tabs();
// Add page title if horizontal tabs are active
$menu_location = get_option('actlogpro_admin_menu_location', 'default');
if (in_array($menu_location, array('dashboard', 'tools'))) {
echo '
';
echo '';
}
// Include the integrations template
include_once ACTLOGPRO_PLUGIN_DIR . 'admin/partials/integrations-display.php';
// Close wrap if opened
if (in_array($menu_location, array('dashboard', 'tools'))) {
echo '
';
}
}
/**
* Display the stats and insights page.
*
* @since 1.0.0
*/
public function display_stats_insights_page() {
// Check user capabilities
if (!current_user_can('manage_options')) {
return;
}
// Display the logo before including the template
$this->display_plugin_logo();
// Display horizontal tabs if menu is under Dashboard or Tools
$this->display_horizontal_tabs();
// Add page title if horizontal tabs are active
$menu_location = get_option('actlogpro_admin_menu_location', 'default');
if (in_array($menu_location, array('dashboard', 'tools'))) {
echo '
';
echo '';
}
// Include the stats and insights template
include_once ACTLOGPRO_PLUGIN_DIR . 'admin/partials/stats-insights-display.php';
// Close wrap if opened
if (in_array($menu_location, array('dashboard', 'tools'))) {
echo '
';
}
}
/**
* Localize stats data for charts.
*
* @since 1.0.0
*/
private function localize_stats_data() {
// Get activity data for the past 30 days
$activity_data = ACTLOGPRO_Database::get_activity_data_by_days(30);
$labels_30 = $activity_data['labels'];
$counts_30 = $activity_data['counts'];
$users_30 = $activity_data['users'];
// Get data for 14 days
$labels_14 = array_slice($labels_30, -14);
$counts_14 = array_slice($counts_30, -14);
$users_14 = array_slice($users_30, -14);
// Get data for 7 days
$labels_7 = array_slice($labels_30, -7);
$counts_7 = array_slice($counts_30, -7);
$users_7 = array_slice($users_30, -7);
// Get event type data
$event_types = ACTLOGPRO_Database::get_event_types_with_counts(8);
$event_labels = array();
$event_counts = array();
$event_colors = array(
'#1e88e5', '#26a69a', '#7cb342', '#ffa000',
'#ef6c00', '#d81b60', '#8e24aa', '#5e35b1',
'#039be5', '#00897b', '#7cb342', '#ffb300'
);
foreach ($event_types as $event) {
$event_labels[] = ucfirst($event['event_type']);
$event_counts[] = (int) $event['count'];
}
// Get action type data
$action_types = ACTLOGPRO_Database::get_action_types_with_counts(8);
$action_labels = array();
$action_counts = array();
$action_colors = array(
'#42a5f5', '#66bb6a', '#ffca28', '#ef5350',
'#ab47bc', '#ec407a', '#7e57c2', '#5c6bc0'
);
foreach ($action_types as $action) {
$action_labels[] = ucfirst($action['action']);
$action_counts[] = (int) $action['count'];
}
// Get activity data by day and hour
$activity_by_time = array();
// Define days of week
$days = array(
__('Sunday', 'activity-log-pro'),
__('Monday', 'activity-log-pro'),
__('Tuesday', 'activity-log-pro'),
__('Wednesday', 'activity-log-pro'),
__('Thursday', 'activity-log-pro'),
__('Friday', 'activity-log-pro'),
__('Saturday', 'activity-log-pro')
);
// Initialize activity data array
foreach ($days as $day_index => $day) {
$activity_by_time[$day_index] = array_fill(0, 24, 0);
}
// Query to get activity by day and hour
$time_results = ACTLOGPRO_Database::get_activity_by_day_and_hour();
// Fill the activity data array
foreach ($time_results as $result) {
$day_index = $result['day_of_week'] - 1;
$hour = $result['hour_of_day'];
$activity_by_time[$day_index][$hour] = (int)$result['count'];
}
// Prepare localized data
$localized_data = array(
'labels_7' => $labels_7,
'counts_7' => $counts_7,
'users_7' => $users_7,
'labels_14' => $labels_14,
'counts_14' => $counts_14,
'users_14' => $users_14,
'labels_30' => $labels_30,
'counts_30' => $counts_30,
'users_30' => $users_30,
'event_labels' => $event_labels,
'event_counts' => $event_counts,
'event_colors' => $event_colors,
'action_labels' => $action_labels,
'action_counts' => $action_counts,
'action_colors' => $action_colors,
'activity_by_time' => $activity_by_time,
'days' => $days,
'labels' => array(
'activities' => __('Activities', 'activity-log-pro'),
'active_users' => __('Active Users', 'activity-log-pro'),
'todays_insights' => __('Today\'s Insights', 'activity-log-pro'),
'view_logs' => __('View logs', 'activity-log-pro'),
)
);
// Localize the script
wp_localize_script('alp-stats-js', 'actlogproStatsData', $localized_data);
}
/**
* Display the upgrade to premium page.
*
* @since 1.0.0
*/
public function display_upgrade_page() {
// Check user capabilities
if (!current_user_can('manage_options')) {
return;
}
// Display the logo before including the template
$this->display_plugin_logo();
// Display horizontal tabs if menu is under Dashboard or Tools
$this->display_horizontal_tabs();
// Add page title if horizontal tabs are active
$menu_location = get_option('actlogpro_admin_menu_location', 'default');
if (in_array($menu_location, array('dashboard', 'tools'))) {
echo '
';
echo '
';
echo '';
echo esc_html(__('Upgrade to Premium', 'activity-log-pro')) . '
';
echo '';
}
// Include the upgrade page template
include_once ACTLOGPRO_PLUGIN_DIR . 'admin/partials/upgrade-display.php';
// Close wrap if opened
if (in_array($menu_location, array('dashboard', 'tools'))) {
echo '
';
}
}
/**
* AJAX handler for generating feed tokens.
*
* @since 1.0.0
*/
public function ajax_generate_feed_token() {
// Check nonce
if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_key(wp_unslash($_POST['nonce'])), 'actlogpro_ajax_nonce')) {
wp_send_json_error(array('message' => __('Invalid security token.', 'activity-log-pro')));
}
// Check user capabilities
if (!current_user_can('manage_options')) {
wp_send_json_error(array('message' => __('You do not have permission to perform this action.', 'activity-log-pro')));
}
// Check if feed type is provided
if (!isset($_POST['feed_type']) || empty($_POST['feed_type'])) {
wp_send_json_error(array('message' => __('Feed type is required.', 'activity-log-pro')));
}
// Get feed type
$feed_type = isset($_POST['feed_type']) ? sanitize_text_field(wp_unslash($_POST['feed_type'])) : '';
// Validate feed type
if ($feed_type !== 'json' && $feed_type !== 'rss') {
wp_send_json_error(array('message' => __('Invalid feed type.', 'activity-log-pro')));
}
// Include the feed class if it's not already included
if (!class_exists('ACTLOGPRO_Feed')) {
require_once ACTLOGPRO_PLUGIN_DIR . 'includes/class-alp-feed.php';
}
// Use a transient to prevent concurrent token generation requests
$transient_key = 'actlogpro_generating_' . $feed_type . '_token';
if (get_transient($transient_key)) {
wp_send_json_error(array(
'message' => __('A token is already being generated. Please wait a moment and try again.', 'activity-log-pro')
));
return;
}
// Set transient to prevent concurrent requests (5 seconds should be enough)
set_transient($transient_key, true, 5);
try {
// Generate new token
$feed = new ACTLOGPRO_Feed();
$token = $feed->generate_token($feed_type);
// Delete the transient
delete_transient($transient_key);
// Return success
wp_send_json_success(array(
'message' => __('Token generated successfully.', 'activity-log-pro'),
'token' => $token
));
} catch (Exception $e) {
// Delete the transient in case of error
delete_transient($transient_key);
// Return error
wp_send_json_error(array(
'message' => __('An error occurred while generating the token: ', 'activity-log-pro') . $e->getMessage()
));
}
}
/**
* AJAX handler for resetting tracking settings.
*
* @since 1.1.0
*/
public function ajax_reset_tracking_settings() {
// Check nonce
if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_key(wp_unslash($_POST['nonce'])), 'actlogpro_ajax_nonce')) {
wp_send_json_error(array('message' => __('Invalid security token.', 'activity-log-pro')));
}
// Check user capabilities
if (!current_user_can('manage_options')) {
wp_send_json_error(array('message' => __('You do not have permission to perform this action.', 'activity-log-pro')));
}
// Reset tracking settings
require_once ACTLOGPRO_PLUGIN_DIR . 'includes/class-alp-events.php';
$events = new ACTLOGPRO_Events();
$reset_settings = $events->reset_tracking_settings();
// Return success
wp_send_json_success(array(
'message' => __('Tracking settings have been reset to default values.', 'activity-log-pro'),
'settings' => $reset_settings
));
}
/**
* AJAX handler for resetting premium settings to defaults.
*
* @since 1.0.0
*/
public function ajax_reset_premium_settings() {
// Check nonce
if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_key(wp_unslash($_POST['nonce'])), 'actlogpro_ajax_nonce')) {
wp_send_json_error(array('message' => __('Security check failed.', 'activity-log-pro')));
}
// Check user capabilities
if (!current_user_can('manage_options')) {
wp_send_json_error(array('message' => __('You do not have permission to perform this action.', 'activity-log-pro')));
}
// Reset premium settings to defaults
ACTLOGPRO_Premium_Helper::reset_premium_settings_to_defaults();
// Return success
wp_send_json_success(array(
'message' => __('Premium settings have been reset to default values.', 'activity-log-pro')
));
}
/**
* Handle AJAX request to filter logs
*
* Security: Requires 'manage_options' capability and valid nonce verification.
*/
public function handle_ajax_filter_logs() {
// Check nonce for security
if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_key(wp_unslash($_POST['nonce'])), 'actlogpro_ajax_nonce')) {
wp_send_json_error(__('Security check failed.', 'activity-log-pro'));
return;
}
// Check user capabilities
if (!current_user_can('manage_options')) {
wp_send_json_error(__('You do not have permission to perform this action.', 'activity-log-pro'));
return;
}
// Rest of the method...
}
/**
* Display horizontal tab navigation for Dashboard/Tools menu locations.
*
* @since 1.0.0
*/
private function display_horizontal_tabs() {
$menu_location = get_option('actlogpro_admin_menu_location', 'default');
// Only show tabs when menu is under Dashboard or Tools
if (!in_array($menu_location, array('dashboard', 'tools'))) {
return;
}
// Get current page slug
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- UI-only tab display, no data processing
$current_page = isset($_GET['page']) ? sanitize_text_field(wp_unslash($_GET['page'])) : 'activity-log-pro';
// Define tab items with icons and URLs
$tabs = array(
'activity-log-pro' => array(
'title' => __('Activity Logs', 'activity-log-pro'),
'icon' => 'dashicons-list-view',
'url' => admin_url('admin.php?page=activity-log-pro')
),
'activity-log-pro-settings' => array(
'title' => __('Settings', 'activity-log-pro'),
'icon' => 'dashicons-admin-settings',
'url' => admin_url('admin.php?page=activity-log-pro-settings')
),
'activity-log-pro-export' => array(
'title' => __('Export', 'activity-log-pro'),
'icon' => 'dashicons-download',
'url' => admin_url('admin.php?page=activity-log-pro-export')
),
'activity-log-pro-integrations' => array(
'title' => __('Integrations', 'activity-log-pro'),
'icon' => 'dashicons-admin-plugins',
'url' => admin_url('admin.php?page=activity-log-pro-integrations')
),
'activity-log-pro-stats' => array(
'title' => __('Stats & Insights', 'activity-log-pro'),
'icon' => 'dashicons-chart-bar',
'url' => admin_url('admin.php?page=activity-log-pro-stats')
)
);
// Add Upgrade to Premium tab if premium UI should be shown
if (ACTLOGPRO_Premium_Helper::show_premium_ui()) {
$tabs['activity-log-pro-upgrade'] = array(
'title' => __('Upgrade to Premium', 'activity-log-pro'),
'icon' => 'dashicons-star-filled',
'url' => admin_url('admin.php?page=activity-log-pro-upgrade')
);
}
echo '
';
// Note: We don't delete the option here - it will be deleted when user dismisses the notice
}
/**
* Display admin notice when premium settings have been reset
*
* @since 1.0.0
*/
public function display_premium_reset_notice() {
// Show once and immediately clear — same behaviour as WP core plugin notices
if ( get_option( 'actlogpro_premium_settings_reset_notice', false ) ) {
delete_option( 'actlogpro_premium_settings_reset_notice' );
$message = __( 'Activity Log Pro - Premium features have been disabled.', 'activity-log-pro' );
echo '
';
echo '
' . esc_html( $message ) . '
';
echo '
';
}
}
/**
* AJAX handler for toggling stats view preference.
*
* @since 1.0.0
*/
public function ajax_toggle_stats_view() {
// Check nonce
if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_key(wp_unslash($_POST['nonce'])), 'actlogpro_ajax_nonce')) {
wp_send_json_error(array('message' => __('Security check failed.', 'activity-log-pro')));
}
// Check if view type is provided
if (!isset($_POST['view_type']) || empty(sanitize_key(wp_unslash($_POST['view_type'])))) {
wp_send_json_error(array('message' => __('View type is required.', 'activity-log-pro')));
}
// Sanitize view type
$view_type = sanitize_key(wp_unslash($_POST['view_type']));
// Set user preference using helper class
$success = ACTLOGPRO_Stats_Helper::set_user_preferred_view($view_type);
if ($success) {
// Get updated stats data - use WordPress local time to match event logging
$today = current_time('Y-m-d');
if ($view_type === 'security') {
$stats_config = ACTLOGPRO_Stats_Helper::get_security_stats_config($today);
$insights = ACTLOGPRO_Stats_Helper::get_security_insights($today);
} else {
$stats_config = ACTLOGPRO_Stats_Helper::get_business_stats_config($today);
$insights = ACTLOGPRO_Stats_Helper::get_business_insights($today);
}
wp_send_json_success(array(
'message' => __('View preference updated successfully.', 'activity-log-pro'),
'view_type' => $view_type,
'stats' => $stats_config,
'insights' => $insights
));
} else {
wp_send_json_error(array('message' => __('Failed to update view preference.', 'activity-log-pro')));
}
}
/**
* AJAX handler for refreshing stats cache
*/
public function ajax_refresh_stats_cache() {
// Check nonce
if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_key(wp_unslash($_POST['nonce'])), 'actlogpro_ajax_nonce')) {
wp_send_json_error(array('message' => __('Security check failed.', 'activity-log-pro')));
}
// Check user capabilities
if (!current_user_can('manage_options')) {
wp_send_json_error(array('message' => __('You do not have permission to perform this action.', 'activity-log-pro')));
}
// Refresh stats cache
ACTLOGPRO_Stats_Helper::refresh_stats_cache();
wp_send_json_success(array('message' => __('Stats cache refreshed successfully.', 'activity-log-pro')));
}
/**
* Add defer attribute to LemonSqueezy script
*
* @param string $tag The script tag
* @param string $handle The script handle
* @return string Modified script tag
*/
public function add_defer_to_lemonsqueezy($tag, $handle) {
if ('lemonsqueezy-checkout' === $handle) {
return str_replace(' src', ' defer src', $tag);
}
return $tag;
}
/**
* Display admin notice when popular caching plugins are detected
*
* @since 1.0.0
*/
public function display_caching_plugin_notice() {
// Check if user has already dismissed this notice
$user_id = get_current_user_id();
$dismissed_notices = get_user_meta($user_id, 'actlogpro_dismissed_notices', true);
if (is_array($dismissed_notices) && isset($dismissed_notices['alp-caching-plugin-notice'])) {
return;
}
// Only show on Activity Log Pro pages
$screen = get_current_screen();
if (!$screen || strpos($screen->id, 'activity-log-pro') === false) {
return;
}
// Detect popular caching plugins
$detected_plugins = $this->detect_caching_plugins();
if (empty($detected_plugins)) {
return;
}
$plugin_names = implode(', ', $detected_plugins);
echo '
';
// translators: %s is the name(s) of detected caching plugin(s)
echo '
' . sprintf(esc_html__('We detected that you have %s active. If you experience issues with RSS/JSON feed tokens not updating, please:', 'activity-log-pro'),
'' . esc_html($plugin_names) . ''
) . '
';
echo '
';
echo '
' . esc_html__('Clear your cache after generating new feed tokens', 'activity-log-pro') . '
';
echo '
' . esc_html__('Configure your caching plugin to exclude feed URLs from caching', 'activity-log-pro') . '
';
echo '
' . esc_html__('Add feed URLs to your caching plugin\'s exclusion list', 'activity-log-pro') . '
';
echo '
';
echo '
' . esc_html__('Feed URLs typically end with /feed/activity-log/ or /wp-json/activity-log-pro/v1/feed/', 'activity-log-pro') . '
';
echo '
';
}
/**
* Detect popular caching plugins
*
* @since 1.0.0
* @return array List of detected caching plugin names
*/
private function detect_caching_plugins() {
$caching_plugins = array(
'litespeed-cache/litespeed-cache.php' => 'LiteSpeed Cache',
'wp-rocket/wp-rocket.php' => 'WP Rocket',
'w3-total-cache/w3-total-cache.php' => 'W3 Total Cache',
'wp-super-cache/wp-cache.php' => 'WP Super Cache',
'wp-fastest-cache/wpFastestCache.php' => 'WP Fastest Cache',
'cache-enabler/cache-enabler.php' => 'Cache Enabler',
'comet-cache/comet-cache.php' => 'Comet Cache',
'hummingbird-performance/wp-hummingbird.php' => 'Hummingbird',
'sg-cachepress/sg-cachepress.php' => 'SG Optimizer',
'wp-optimize/wp-optimize.php' => 'WP-Optimize',
'autoptimize/autoptimize.php' => 'Autoptimize',
'breeze/breeze.php' => 'Breeze',
'swift-performance-lite/performance.php' => 'Swift Performance',
'hyper-cache/plugin.php' => 'Hyper Cache'
);
$detected = array();
foreach ($caching_plugins as $plugin_file => $plugin_name) {
if (is_plugin_active($plugin_file)) {
$detected[] = $plugin_name;
}
}
return $detected;
}
/**
* AJAX handler for adding object to exclusions
* Adds an option name to the Excluded Option Names list
*
* @since 1.0.0
*/
public function ajax_add_object_to_exclusions() {
// Check nonce
if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_key(wp_unslash($_POST['nonce'])), 'actlogpro_ajax_nonce')) {
wp_send_json_error(array('message' => __('Security check failed.', 'activity-log-pro')));
return;
}
// Check user capabilities
if (!current_user_can('manage_options')) {
wp_send_json_error(array('message' => __('You do not have permission to perform this action.', 'activity-log-pro')));
return;
}
// Check for required data
if (!isset($_POST['object_name']) || empty($_POST['object_name'])) {
wp_send_json_error(array('message' => __('Object name is required.', 'activity-log-pro')));
return;
}
// Sanitize the object name
$object_name = sanitize_text_field(wp_unslash($_POST['object_name']));
// Get current excluded option names
$excluded_options = get_option('actlogpro_excluded_option_names', '');
// Convert to array
$excluded_array = array_filter(array_map('trim', explode("\n", $excluded_options)));
// Check if already excluded
if (in_array($object_name, $excluded_array, true)) {
wp_send_json_error(array('message' => __('This option is already in the exclusion list.', 'activity-log-pro')));
return;
}
// Add to exclusions
$excluded_array[] = $object_name;
// Convert back to string
$excluded_string = implode("\n", $excluded_array);
// Update option
$updated = update_option('actlogpro_excluded_option_names', $excluded_string);
if ($updated || in_array($object_name, $excluded_array, true)) {
wp_send_json_success(array(
'message' => sprintf(
/* translators: %s is the option name added to exclusions */
__('Added "%s" to exclusions. Refresh to see changes.', 'activity-log-pro'),
$object_name
)
));
} else {
wp_send_json_error(array('message' => __('Failed to add exclusion. Please try again.', 'activity-log-pro')));
}
}
/**
* AJAX handler for logging quick edit changes
*
* @since 1.0.0
*/
public function ajax_log_quick_edit() {
// Check nonce
if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_key(wp_unslash($_POST['nonce'])), 'actlogpro_ajax_nonce')) {
wp_send_json_error(array('message' => __('Security check failed.', 'activity-log-pro')));
return;
}
// Check user capabilities - must have manage_options (admin access required)
if (!current_user_can('manage_options')) {
wp_send_json_error(array('message' => __('You do not have permission to perform this action.', 'activity-log-pro')));
return;
}
// Check for required data
if (!isset($_POST['post_id']) || !isset($_POST['old_title']) || !isset($_POST['new_title'])) {
wp_send_json_error(array('message' => __('Missing required data.', 'activity-log-pro')));
return;
}
$post_id = absint(wp_unslash($_POST['post_id']));
$old_title = sanitize_text_field(wp_unslash($_POST['old_title']));
$new_title = sanitize_text_field(wp_unslash($_POST['new_title']));
// Get post
$post = get_post($post_id);
if (!$post) {
wp_send_json_error(array('message' => __('Post not found.', 'activity-log-pro')));
return;
}
// Check if user can edit this specific post
if (!current_user_can('edit_post', $post_id)) {
wp_send_json_error(array('message' => __('Insufficient permissions to edit this post.', 'activity-log-pro')));
return;
}
// Only log if tracking is enabled
if (get_option('actlogpro_track_post_events', '1') === '1') {
// Log the activity
ACTLOGPRO_Logger::log_activity(
'post',
$post->post_type,
$post_id,
$new_title,
'update',
sprintf(
/* translators: %1$s: old title, %2$s: new title */
__('Title changed from "%1$s" to "%2$s" using Quick Edit', 'activity-log-pro'),
$old_title,
$new_title
),
array(
'old_title' => $old_title,
'new_title' => $new_title
)
);
}
wp_send_json_success();
}
/**
* AJAX handler for getting current plugin settings
*
* @since 1.0.0
*/
public function ajax_get_settings() {
// Verify nonce
if (!isset($_POST['nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['nonce'])), 'actlogpro_ajax_nonce')) {
wp_send_json_error(__('Invalid security token.', 'activity-log-pro'));
return;
}
// Get current settings
$settings = array(
'items_per_page' => get_option('actlogpro_items_per_page', 25)
);
wp_send_json_success($settings);
}
/**
* Ensure email digest is properly scheduled on admin init.
*
* Only runs once per session and only schedules if needed.
* Prevents performance issues while ensuring scheduling happens.
*
* @since 1.1.0
*/
public function ensure_email_digest_scheduled() {
// Only run once per session to avoid repeated checks
static $already_checked = false;
if ($already_checked) {
return;
}
$already_checked = true;
// Only schedule if email digest class is available
if (class_exists('ACTLOGPRO_Email_Digest_Manager')) {
ACTLOGPRO_Email_Digest_Manager::schedule_email_digest();
}
}
/**
* Render the admin footer for all plugin pages
*
* @since 1.0.4
*/
public function actlogpro_render_admin_footer() {
?>
__( 'Security check failed.', 'activity-log-pro' ) ) );
}
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => __( 'You do not have permission to perform this action.', 'activity-log-pro' ) ) );
}
// Prevent any server or browser caching of live session data.
nocache_headers();
$args = array(
'page' => isset( $_POST['page'] ) ? absint( wp_unslash( $_POST['page'] ) ) : 1,
'per_page' => isset( $_POST['per_page'] ) ? absint( wp_unslash( $_POST['per_page'] ) ) : 0,
'search' => isset( $_POST['search'] ) ? sanitize_text_field( wp_unslash( $_POST['search'] ) ) : '',
'role' => isset( $_POST['role'] ) ? sanitize_text_field( wp_unslash( $_POST['role'] ) ) : '',
'multi_only' => ! empty( $_POST['multi_only'] ),
);
if ( ! class_exists( 'ACTLOGPRO_Live_Sessions' ) ) {
require_once ACTLOGPRO_PLUGIN_DIR . 'includes/class-alp-live-sessions.php';
}
$result = ACTLOGPRO_Live_Sessions::get_active_sessions( $args );
// Allow premium to enrich sessions (e.g. geolocation) before sending to client.
foreach ( $result['sessions'] as &$session ) {
$session = apply_filters( 'actlogpro_live_session_data', $session );
// Strip raw IP after enrichment — only display-safe data goes to the client.
unset( $session['ip_raw'] );
}
unset( $session );
wp_send_json_success( $result );
}
/**
* AJAX handler: terminate a specific user session.
* Premium-gated. Includes self-termination guard with confirmation step.
*
* @since 1.1.0
*/
public function ajax_terminate_session() {
if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_key( wp_unslash( $_POST['nonce'] ) ), 'actlogpro_ajax_nonce' ) ) {
wp_send_json_error( array( 'message' => __( 'Security check failed.', 'activity-log-pro' ) ) );
}
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => __( 'You do not have permission to perform this action.', 'activity-log-pro' ) ) );
}
if ( ! ACTLOGPRO_Premium_Helper::is_premium_enabled() ) {
wp_send_json_error( array( 'message' => __( 'This feature requires Activity Log Pro Premium.', 'activity-log-pro' ), 'premium_required' => true ) );
}
$target_user_id = isset( $_POST['user_id'] ) ? absint( wp_unslash( $_POST['user_id'] ) ) : 0;
$token_hash = isset( $_POST['token_hash'] ) ? sanitize_text_field( wp_unslash( $_POST['token_hash'] ) ) : '';
$confirmed = isset( $_POST['confirmed'] ) && sanitize_text_field( wp_unslash( $_POST['confirmed'] ) ) === '1';
if ( ! $target_user_id || empty( $token_hash ) ) {
wp_send_json_error( array( 'message' => __( 'Invalid session data.', 'activity-log-pro' ) ) );
}
// Validate token hash is hex-string only (sha256 output) to prevent injection.
if ( ! preg_match( '/^[a-f0-9]{40,64}$/i', $token_hash ) ) {
wp_send_json_error( array( 'message' => __( 'Invalid session identifier.', 'activity-log-pro' ) ) );
}
// Self-termination guard: require explicit confirmation.
$current_hash = ACTLOGPRO_Live_Sessions::get_current_session_hash();
if ( $token_hash === $current_hash && ! $confirmed ) {
wp_send_json_error( array(
'message' => __( 'Confirmation required.', 'activity-log-pro' ),
'requires_confirmation' => true,
) );
}
// Delegate actual termination to the premium class.
if ( ! class_exists( 'ACTLOGPRO_Live_Sessions_Premium' ) ) {
wp_send_json_error( array( 'message' => __( 'Premium module not loaded.', 'activity-log-pro' ) ) );
}
$result = ACTLOGPRO_Live_Sessions_Premium::terminate_session( $target_user_id, $token_hash );
if ( $result ) {
wp_send_json_success( array( 'message' => __( 'Session terminated successfully.', 'activity-log-pro' ) ) );
} else {
wp_send_json_error( array( 'message' => __( 'Failed to terminate session. The session may have already expired.', 'activity-log-pro' ) ) );
}
}
/**
* AJAX handler: emergency lockdown — terminate all sessions except the current admin's own.
* Premium-gated. Requires explicit double-confirmation token.
*
* @since 1.1.0
*/
public function ajax_emergency_lockdown() {
if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_key( wp_unslash( $_POST['nonce'] ) ), 'actlogpro_ajax_nonce' ) ) {
wp_send_json_error( array( 'message' => __( 'Security check failed.', 'activity-log-pro' ) ) );
}
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => __( 'You do not have permission to perform this action.', 'activity-log-pro' ) ) );
}
if ( ! ACTLOGPRO_Premium_Helper::is_premium_enabled() ) {
wp_send_json_error( array( 'message' => __( 'This feature requires Activity Log Pro Premium.', 'activity-log-pro' ), 'premium_required' => true ) );
}
$confirmed = isset( $_POST['confirmed'] ) && sanitize_text_field( wp_unslash( $_POST['confirmed'] ) ) === '1';
if ( ! $confirmed ) {
wp_send_json_error( array( 'message' => __( 'Confirmation required.', 'activity-log-pro' ), 'requires_confirmation' => true ) );
}
if ( ! class_exists( 'ACTLOGPRO_Live_Sessions_Premium' ) ) {
wp_send_json_error( array( 'message' => __( 'Premium module not loaded.', 'activity-log-pro' ) ) );
}
$current_hash = ACTLOGPRO_Live_Sessions::get_current_session_hash();
$count = ACTLOGPRO_Live_Sessions_Premium::terminate_all_except( get_current_user_id(), $current_hash );
wp_send_json_success( array(
'message' => sprintf(
/* translators: %d number of sessions terminated */
_n( 'Lockdown complete. %d session terminated.', 'Lockdown complete. %d sessions terminated.', $count, 'activity-log-pro' ),
$count
),
'count' => $count,
) );
}
/**
* AJAX handler: save Live Sessions settings (per-page, simultaneous limit).
*
* @since 1.1.0
*/
public function ajax_save_live_sessions_settings() {
if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_key( wp_unslash( $_POST['nonce'] ) ), 'actlogpro_ajax_nonce' ) ) {
wp_send_json_error( array( 'message' => __( 'Security check failed.', 'activity-log-pro' ) ) );
}
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => __( 'You do not have permission to perform this action.', 'activity-log-pro' ) ) );
}
// Sessions per page (free + premium).
if ( isset( $_POST['per_page'] ) ) {
$per_page = max( 1, min( 200, absint( wp_unslash( $_POST['per_page'] ) ) ) );
update_option( ACTLOGPRO_Live_Sessions::PER_PAGE_OPTION, $per_page );
}
// Active threshold minutes (free + premium).
if ( isset( $_POST['active_minutes'] ) && class_exists( 'ACTLOGPRO_Live_Sessions_Activity' ) ) {
$active_minutes = ACTLOGPRO_Live_Sessions_Activity::sanitize_threshold_minutes(
absint( wp_unslash( $_POST['active_minutes'] ) )
);
update_option( ACTLOGPRO_Live_Sessions_Activity::THRESHOLD_OPTION, $active_minutes );
}
// Simultaneous session limit — premium only.
if ( isset( $_POST['simultaneous_limit'] ) && ACTLOGPRO_Premium_Helper::is_premium_enabled() ) {
$limit = max( 0, absint( wp_unslash( $_POST['simultaneous_limit'] ) ) );
update_option( 'actlogpro_live_sessions_simultaneous_limit', $limit );
}
wp_send_json_success( array( 'message' => __( 'Settings saved.', 'activity-log-pro' ) ) );
}
}