register_ajax_handlers(); // Get log details add_action('wp_ajax_actlogpro_get_log_details', array($this, 'get_log_details')); // Schedule cleanup task add_action('actlogpro_cleanup_old_logs', array($this, 'cleanup_old_logs')); if (!wp_next_scheduled('actlogpro_cleanup_old_logs')) { wp_schedule_event(time(), 'daily', 'actlogpro_cleanup_old_logs'); } // Schedule email digest if enabled and not already scheduled // This ensures scheduling happens on plugin load if needed add_action('admin_init', array($this, 'ensure_email_digest_scheduled'), 20); // Add admin notice for premium settings reset add_action('admin_notices', array($this, 'display_premium_reset_notice')); // Add admin notice for caching plugin compatibility add_action('admin_notices', array($this, 'display_caching_plugin_notice')); // Add admin notice for plugin activation add_action('admin_notices', array($this, 'display_activation_notice')); // Initialize rating request functionality add_action('init', array($this, 'init_rating_request')); } /** * Initialize rating request functionality. * * @since 1.0.3 */ public function init_rating_request() { // Only run for administrators if (!current_user_can('manage_options')) { return; } // Load rating request class if not already loaded if (!class_exists('ACTLOGPRO_Rating_Request')) { require_once ACTLOGPRO_PLUGIN_DIR . 'includes/class-alp-rating-request.php'; } // Initialize rating request functionality ACTLOGPRO_Rating_Request::init(); } /** * Register all AJAX handlers for the plugin * * @since 1.0.0 */ public function register_ajax_handlers() { add_action('wp_ajax_actlogpro_get_activity_logs', array($this, 'ajax_get_activity_logs')); add_action('wp_ajax_actlogpro_clear_logs', array($this, 'ajax_clear_logs')); add_action('wp_ajax_actlogpro_delete_selected_logs', array($this, 'ajax_delete_selected_logs')); add_action('wp_ajax_actlogpro_delete_logs', array($this, 'ajax_delete_selected_logs')); // Add this line to handle the correct action name add_action('wp_ajax_actlogpro_filter_logs', array($this, 'handle_ajax_filter_logs')); add_action('wp_ajax_actlogpro_save_settings', array($this, 'save_settings')); add_action('wp_ajax_actlogpro_generate_feed_token', array($this, 'ajax_generate_feed_token')); add_action('wp_ajax_actlogpro_reset_tracking_settings', array($this, 'ajax_reset_tracking_settings')); add_action('wp_ajax_actlogpro_reset_premium_settings', array($this, 'ajax_reset_premium_settings')); add_action('wp_ajax_actlogpro_dismiss_notice', array($this, 'ajax_dismiss_notice')); add_action('wp_ajax_actlogpro_get_ip_info', array($this, 'ajax_get_ip_info')); add_action('wp_ajax_actlogpro_toggle_stats_view', array($this, 'ajax_toggle_stats_view')); add_action('wp_ajax_actlogpro_refresh_stats_cache', array($this, 'ajax_refresh_stats_cache')); add_action('wp_ajax_actlogpro_log_quick_edit', array($this, 'ajax_log_quick_edit')); add_action('wp_ajax_actlogpro_get_settings', array($this, 'ajax_get_settings')); add_action('wp_ajax_actlogpro_add_object_to_exclusions', array($this, 'ajax_add_object_to_exclusions')); add_action('wp_ajax_actlogpro_dismiss_rating_request', array('ACTLOGPRO_Rating_Request', 'ajax_handle_dismissal')); } /** * AJAX handler for dismissing admin notices permanently. * * Security: Requires 'manage_options' capability and valid nonce verification. * Allows administrators to dismiss plugin notices and alerts. * * @since 1.0.0 */ public function ajax_dismiss_notice() { // 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 notice ID is provided if (!isset($_POST['notice_id']) || empty(sanitize_key(wp_unslash($_POST['notice_id'])))) { wp_send_json_error(array('message' => __('Notice ID is required.', 'activity-log-pro'))); } // Sanitize notice ID $notice_id = sanitize_key(wp_unslash($_POST['notice_id'])); // Handle special case for premium settings reset notice if ($notice_id === 'premium_settings_reset') { // Check user capabilities for global notice dismissal if (!current_user_can('manage_options')) { wp_send_json_error(array('message' => __('You do not have permission to perform this action.', 'activity-log-pro'))); } // Remove the global premium reset notice option delete_option('actlogpro_premium_settings_reset_notice'); wp_send_json_success(array('message' => __('Notice dismissed successfully.', 'activity-log-pro'))); return; } // Handle special case for activation notice if ($notice_id === 'alp-activation-notice') { // For activation notice, we need to handle it as user-specific // The global flag will be cleared when ALL users have dismissed it // For now, just continue with normal user-specific handling } // Handle regular user-specific notices // Get current user ID $user_id = get_current_user_id(); if (!$user_id) { wp_send_json_error(array('message' => __('User not logged in.', 'activity-log-pro'))); } // Get dismissed notices for this user $dismissed_notices = get_user_meta($user_id, 'actlogpro_dismissed_notices', true); if (!is_array($dismissed_notices)) { $dismissed_notices = array(); } // Check if notice is already dismissed if (isset($dismissed_notices[$notice_id])) { wp_send_json_success(array('message' => __('Notice was already dismissed.', 'activity-log-pro'))); return; } // Add this notice to the dismissed list $dismissed_notices[$notice_id] = time(); // Update user meta $updated = update_user_meta($user_id, 'actlogpro_dismissed_notices', $dismissed_notices); // update_user_meta returns false if the value hasn't changed, which is not an error // So we check if the value is properly stored by retrieving it $verify_dismissed = get_user_meta($user_id, 'actlogpro_dismissed_notices', true); if (is_array($verify_dismissed) && isset($verify_dismissed[$notice_id])) { // Special handling for activation notice - check if all users have dismissed it if ($notice_id === 'alp-activation-notice') { $this->check_activation_notice_global_dismissal(); } wp_send_json_success(array('message' => __('Notice dismissed successfully.', 'activity-log-pro'))); } else { wp_send_json_error(array('message' => __('Failed to dismiss notice.', 'activity-log-pro'))); } } /** * Check if all users have dismissed the activation notice and clear global flag if so * * @since 1.0.1 */ private function check_activation_notice_global_dismissal() { // Get all users who can manage options $users = get_users(array( 'capability' => 'manage_options', 'fields' => 'ID' )); if (empty($users)) { return; } // Check if all users have dismissed the activation notice $all_dismissed = true; foreach ($users as $user_id) { $dismissed_notices = get_user_meta($user_id, 'actlogpro_dismissed_notices', true); if (!is_array($dismissed_notices) || !isset($dismissed_notices['alp-activation-notice'])) { $all_dismissed = false; break; } } // If all users have dismissed it, clear the global flag if ($all_dismissed) { delete_option('actlogpro_show_activation_notice'); } } /** * Register the admin menu. * * @since 1.0.0 */ public function add_admin_menu() { // Get the admin menu location setting $menu_location = get_option('actlogpro_admin_menu_location', 'default'); // Define menu parameters $page_title = __('Activity Log Pro', 'activity-log-pro'); $menu_title = __('Activity Log Pro', 'activity-log-pro'); $capability = 'manage_options'; $menu_slug = 'activity-log-pro'; $callback = array($this, 'display_activity_log_page'); // Custom SVG icon - task completion/checklist optimized for WordPress standards $svg_icon = ''; $icon = 'data:image/svg+xml;base64,' . base64_encode($svg_icon); // Add main menu based on location setting switch ($menu_location) { case 'dashboard': // Add as submenu under Dashboard add_submenu_page( 'index.php', $page_title, $menu_title, $capability, $menu_slug, $callback ); break; case 'tools': // Add as submenu under Tools add_submenu_page( 'tools.php', $page_title, $menu_title, $capability, $menu_slug, $callback ); break; case 'top': // Add at top of main menu (position 3) add_menu_page( $page_title, $menu_title, $capability, $menu_slug, $callback, $icon, 3 ); break; case 'bottom': // Add at bottom of main menu (position 99) add_menu_page( $page_title, $menu_title, $capability, $menu_slug, $callback, $icon, 99 ); break; default: // Default WordPress menu order (position 30) add_menu_page( $page_title, $menu_title, $capability, $menu_slug, $callback, $icon, 30 ); break; } // Only add Activity Log submenu if we're not under Dashboard or Tools if (!in_array($menu_location, array('dashboard', 'tools'))) { add_submenu_page( $menu_slug, __('Activity Logs', 'activity-log-pro'), __('Activity Logs', 'activity-log-pro'), $capability, $menu_slug ); } // For Dashboard/Tools, register submenus under the appropriate parent if (in_array($menu_location, array('dashboard', 'tools'))) { // Register submenus directly under their respective parent pages $parent_page = ($menu_location === 'dashboard') ? 'index.php' : 'tools.php'; // Add Settings submenu (properly registered to avoid PHP 8.1+ warnings) $settings_hook = add_submenu_page( $parent_page, __('Activity Log Pro Settings', 'activity-log-pro'), __('ALP Settings', 'activity-log-pro'), $capability, 'activity-log-pro-settings', array($this, 'display_settings_page') ); // Add Export submenu (properly registered to avoid PHP 8.1+ warnings) $export_hook = add_submenu_page( $parent_page, __('Activity Log Pro Export', 'activity-log-pro'), __('ALP Export', 'activity-log-pro'), $capability, 'activity-log-pro-export', array($this, 'display_export_page') ); // Add Integrations submenu (properly registered to avoid PHP 8.1+ warnings) $integrations_hook = add_submenu_page( $parent_page, __('Activity Log Pro Integrations', 'activity-log-pro'), __('ALP Integrations', 'activity-log-pro'), $capability, 'activity-log-pro-integrations', array($this, 'display_integrations_page') ); // Add Stats & Insights submenu (properly registered to avoid PHP 8.1+ warnings) $stats_hook = add_submenu_page( $parent_page, __('Activity Log Pro Stats', 'activity-log-pro'), __('ALP Stats', 'activity-log-pro'), $capability, 'activity-log-pro-stats', array($this, 'display_stats_insights_page') ); // Add Upgrade to Premium submenu (properly registered to avoid PHP 8.1+ warnings) if (ACTLOGPRO_Premium_Helper::show_premium_ui()) { $upgrade_hook = add_submenu_page( $parent_page, __('Activity Log Pro Upgrade', 'activity-log-pro'), __('ALP Upgrade', 'activity-log-pro'), $capability, 'activity-log-pro-upgrade', array($this, 'display_upgrade_page') ); } // Hide the submenus from the visible menu while keeping them accessible via direct URL add_action('admin_head', function() use ($parent_page) { global $submenu; // Remove our plugin pages from the visible submenu while keeping them registered if (isset($submenu[$parent_page])) { foreach ($submenu[$parent_page] as $key => $menu_item) { if (isset($menu_item[2]) && strpos($menu_item[2], 'activity-log-pro-') === 0) { unset($submenu[$parent_page][$key]); } } } }); } else { // For regular menu locations, add submenus normally add_submenu_page( $menu_slug, __('Settings', 'activity-log-pro'), __('Settings', 'activity-log-pro'), $capability, 'activity-log-pro-settings', array($this, 'display_settings_page') ); add_submenu_page( $menu_slug, __('Export', 'activity-log-pro'), __('Export', 'activity-log-pro'), $capability, 'activity-log-pro-export', array($this, 'display_export_page') ); add_submenu_page( $menu_slug, __('Integrations', 'activity-log-pro'), __('Integrations', 'activity-log-pro'), $capability, 'activity-log-pro-integrations', array($this, 'display_integrations_page') ); add_submenu_page( $menu_slug, __('Stats & Insights', 'activity-log-pro'), __('Stats & Insights', 'activity-log-pro'), $capability, 'activity-log-pro-stats', array($this, 'display_stats_insights_page') ); // Add Upgrade to Premium submenu (only show if premium is not enabled) if (ACTLOGPRO_Premium_Helper::show_premium_ui()) { add_submenu_page( $menu_slug, __('Upgrade to Premium', 'activity-log-pro'), __('Upgrade to Premium', 'activity-log-pro'), $capability, 'activity-log-pro-upgrade', array($this, 'display_upgrade_page') ); } } } /** * Register plugin settings. * * @since 1.0.0 */ public function register_settings() { register_setting('actlogpro_activity_log_pro_settings', 'actlogpro_retention_period', array( 'sanitize_callback' => array($this, 'sanitize_integer') )); register_setting('actlogpro_activity_log_pro_settings', 'actlogpro_items_per_page', array( 'sanitize_callback' => array($this, 'sanitize_integer') )); register_setting('actlogpro_activity_log_pro_settings', 'actlogpro_track_user_events', array( 'sanitize_callback' => array($this, 'sanitize_checkbox') )); register_setting('actlogpro_activity_log_pro_settings', 'actlogpro_track_post_events', array( 'sanitize_callback' => array($this, 'sanitize_checkbox') )); register_setting('actlogpro_activity_log_pro_settings', 'actlogpro_track_media_events', array( 'sanitize_callback' => array($this, 'sanitize_checkbox') )); register_setting('actlogpro_activity_log_pro_settings', 'actlogpro_track_comment_events', array( 'sanitize_callback' => array($this, 'sanitize_checkbox') )); register_setting('actlogpro_activity_log_pro_settings', 'actlogpro_track_plugin_events', array( 'sanitize_callback' => array($this, 'sanitize_checkbox') )); register_setting('actlogpro_activity_log_pro_settings', 'actlogpro_track_theme_events', array( 'sanitize_callback' => array($this, 'sanitize_checkbox') )); register_setting('actlogpro_activity_log_pro_settings', 'actlogpro_track_widget_events', array( 'sanitize_callback' => array($this, 'sanitize_checkbox') )); register_setting('actlogpro_activity_log_pro_settings', 'actlogpro_track_menu_events', array( 'sanitize_callback' => array($this, 'sanitize_checkbox') )); register_setting('actlogpro_activity_log_pro_settings', 'actlogpro_track_option_events', array( 'sanitize_callback' => array($this, 'sanitize_checkbox') )); register_setting('actlogpro_activity_log_pro_settings', 'actlogpro_track_wordpress_events', array( 'sanitize_callback' => array($this, 'sanitize_checkbox') )); register_setting('actlogpro_activity_log_pro_settings', 'actlogpro_track_security_events', array( 'sanitize_callback' => array($this, 'sanitize_checkbox') )); register_setting('actlogpro_activity_log_pro_settings', 'actlogpro_flood_guard_enabled', array( 'sanitize_callback' => array($this, 'sanitize_checkbox') )); register_setting('actlogpro_activity_log_pro_settings', 'actlogpro_flood_guard_limit', array( 'sanitize_callback' => array($this, 'sanitize_flood_guard_limit') )); // Event filtering and exclusions register_setting('actlogpro_activity_log_pro_settings', 'actlogpro_excluded_option_names', array( 'sanitize_callback' => array($this, 'sanitize_excluded_options') )); register_setting('actlogpro_activity_log_pro_settings', 'actlogpro_excluded_post_types', array( 'type' => 'array', 'sanitize_callback' => array($this, 'sanitize_excluded_post_types'), 'default' => array() )); register_setting('actlogpro_activity_log_pro_settings', 'actlogpro_exclude_attachments_for_excluded_posts', array( 'type' => 'boolean', 'sanitize_callback' => array($this, 'sanitize_checkbox'), 'default' => true )); register_setting('actlogpro_activity_log_pro_settings', 'actlogpro_exclude_deleted_attachments_for_excluded_posts', array( 'type' => 'boolean', 'sanitize_callback' => array($this, 'sanitize_checkbox'), 'default' => false )); // Feed settings register_setting('actlogpro_activity_log_pro_settings', 'actlogpro_enable_json_feed', array( 'sanitize_callback' => array($this, 'sanitize_checkbox') )); register_setting('actlogpro_activity_log_pro_settings', 'actlogpro_enable_rss_feed', array( 'sanitize_callback' => array($this, 'sanitize_checkbox') )); // UI settings register_setting('actlogpro_activity_log_pro_settings', 'actlogpro_allow_multiple_expanded_rows', array( 'sanitize_callback' => array($this, 'sanitize_checkbox') )); // IP Address settings - register for all users (premium validation happens in save_settings) register_setting('actlogpro_activity_log_pro_settings', 'actlogpro_store_full_ip', array( 'sanitize_callback' => array($this, 'sanitize_checkbox') )); // Misc settings register_setting('actlogpro_activity_log_pro_settings', 'actlogpro_login_notification_enabled', array( 'sanitize_callback' => array($this, 'sanitize_checkbox') )); register_setting('actlogpro_activity_log_pro_settings', 'actlogpro_login_notification_text', array( 'sanitize_callback' => 'wp_kses_post' )); register_setting('actlogpro_activity_log_pro_settings', 'actlogpro_admin_menu_location', array( 'sanitize_callback' => 'sanitize_text_field' )); register_setting('actlogpro_activity_log_pro_settings', 'actlogpro_remove_data_on_uninstall', array( 'sanitize_callback' => array($this, 'sanitize_checkbox') )); // Register email digest settings here with other settings register_setting('actlogpro_activity_log_pro_settings', 'actlogpro_email_digest_enabled', array( 'sanitize_callback' => array('ACTLOGPRO_Email_Digest_Manager', 'sanitize_checkbox') )); register_setting('actlogpro_activity_log_pro_settings', 'actlogpro_email_digest_frequency', array( 'sanitize_callback' => array('ACTLOGPRO_Email_Digest_Manager', 'sanitize_frequency') )); register_setting('actlogpro_activity_log_pro_settings', 'actlogpro_email_digest_day', array( 'sanitize_callback' => array('ACTLOGPRO_Email_Digest_Manager', 'sanitize_day') )); register_setting('actlogpro_activity_log_pro_settings', 'actlogpro_email_digest_time', array( 'sanitize_callback' => array('ACTLOGPRO_Email_Digest_Manager', 'sanitize_time') )); register_setting('actlogpro_activity_log_pro_settings', 'actlogpro_email_digest_recipients', array( 'sanitize_callback' => array('ACTLOGPRO_Email_Digest_Manager', 'sanitize_email_recipients') )); register_setting('actlogpro_activity_log_pro_settings', 'actlogpro_email_digest_last_sent', array( 'sanitize_callback' => 'absint' )); } /** * Handle settings save for checkbox arrays * WordPress doesn't submit unchecked checkboxes, so we need to handle empty arrays manually * * @since 1.0.0 */ public function handle_settings_save() { // Check if we're saving settings // phpcs:ignore WordPress.Security.NonceVerification.Missing -- WordPress Settings API handles nonce verification if (!isset($_POST['option_page']) || $_POST['option_page'] !== 'actlogpro_activity_log_pro_settings') { return; } // Verify nonce for security // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Nonce verification doesn't require sanitization if (!isset($_POST['_wpnonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['_wpnonce'])), 'actlogpro_activity_log_pro_settings-options')) { return; } // Check if our hidden field is present (means the excluded post types field was on the form) // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Already verified nonce above if (!isset($_POST['actlogpro_excluded_post_types_submitted'])) { return; } // Get the submitted value (empty array if no checkboxes checked) // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Nonce verified above, sanitized by sanitize_excluded_post_types() method below $submitted_value = isset($_POST['actlogpro_excluded_post_types']) ? wp_unslash($_POST['actlogpro_excluded_post_types']) : array(); // Sanitize using our existing method $sanitized_value = $this->sanitize_excluded_post_types($submitted_value); // Directly update the option since WordPress Settings API isn't calling our callback update_option('actlogpro_excluded_post_types', $sanitized_value); // Handle the attachment exclusion checkboxes (WordPress doesn't submit unchecked checkboxes) // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Already verified nonce above $exclude_attachments = isset($_POST['actlogpro_exclude_attachments_for_excluded_posts']) ? '1' : '0'; update_option('actlogpro_exclude_attachments_for_excluded_posts', $exclude_attachments); // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Already verified nonce above $exclude_deleted_attachments = isset($_POST['actlogpro_exclude_deleted_attachments_for_excluded_posts']) ? '1' : '0'; update_option('actlogpro_exclude_deleted_attachments_for_excluded_posts', $exclude_deleted_attachments); } /** * Sanitize integer values * * @param mixed $value The value to sanitize * @return int Sanitized integer value */ public function sanitize_integer($value) { return absint($value); } /** * Sanitize checkbox values * * @param mixed $value The value to sanitize * @return string '1' if truthy, '0' otherwise */ public function sanitize_checkbox($value) { $result = !empty($value) ? '1' : '0'; return $result; } /** * Sanitize flood guard limit — only allow specific whitelist values. */ public function sanitize_flood_guard_limit($value) { $allowed = array(20, 50, 100, 200); $value = (int) $value; return in_array($value, $allowed, true) ? $value : 50; } /** * Sanitize excluded option names textarea * * @param string $value The value to sanitize * @return string Sanitized option names, one per line */ public function sanitize_excluded_options($value) { if (empty($value)) { return ''; } // Normalize line endings first $value = str_replace(array("\r\n", "\r"), "\n", $value); // Split by both newlines AND spaces to handle form submission issues $items = preg_split('/[\s\n]+/', trim($value), -1, PREG_SPLIT_NO_EMPTY); // Process each item $sanitized_lines = array(); foreach ($items as $item) { $item = trim($item); if (!empty($item)) { // Sanitize but preserve underscores, wildcards, etc. $item = wp_strip_all_tags($item); if (!empty($item)) { $sanitized_lines[] = $item; } } } // Join back with newlines return implode("\n", $sanitized_lines); } /** * Sanitize excluded post types setting * * @param mixed $value The value to sanitize * @return array Sanitized array of post type slugs */ public function sanitize_excluded_post_types($value) { // If no value provided, return empty array if (empty($value) || !is_array($value)) { return array(); } // Sanitize each post type slug $sanitized = array_map('sanitize_key', $value); // Validate that each post type actually exists $valid_post_types = array(); foreach ($sanitized as $post_type) { if (post_type_exists($post_type)) { $valid_post_types[] = $post_type; } } return $valid_post_types; } /** * Enqueue admin scripts and styles. * * @since 1.0.0 * @param string $hook Current admin page. */ public function enqueue_scripts($hook) { // Enqueue rating request styles on all admin pages (rating notices can appear anywhere) wp_enqueue_style('alp-rating-request-css', ACTLOGPRO_PLUGIN_URL . 'admin/css/alp-rating-request.css', array(), ACTLOGPRO_VERSION); wp_enqueue_script('alp-rating-request-js', ACTLOGPRO_PLUGIN_URL . 'admin/js/alp-rating-request.js', array('jquery'), ACTLOGPRO_VERSION, true); // Enqueue notices script on all admin pages (notices can appear anywhere) wp_enqueue_script('alp-notices-js', ACTLOGPRO_PLUGIN_URL . 'admin/js/alp-notices.js', array('jquery'), ACTLOGPRO_VERSION, true); // Localize notices script with AJAX data wp_localize_script('alp-notices-js', 'actlogproAdminData', array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('actlogpro_ajax_nonce') )); // Only load plugin-specific assets on plugin pages if (strpos($hook, 'activity-log-pro') === false) { return; } // Enqueue styles wp_enqueue_style('alp-admin-css', ACTLOGPRO_PLUGIN_URL . 'admin/css/alp-admin.css', array(), ACTLOGPRO_VERSION); wp_enqueue_style('alp-tooltip-fix', ACTLOGPRO_PLUGIN_URL . 'admin/css/alp-tooltip-fix.css', array('alp-admin-css'), ACTLOGPRO_VERSION); wp_enqueue_style('alp-export-css', ACTLOGPRO_PLUGIN_URL . 'admin/css/alp-export.css', array('alp-admin-css'), ACTLOGPRO_VERSION); // Note: License management scripts are now handled by the premium plugin // Enqueue jQuery UI styles and scripts wp_enqueue_style('jquery-ui-css', ACTLOGPRO_PLUGIN_URL . 'admin/css/jquery-ui.min.css', array(), '1.12.1'); wp_enqueue_script('jquery-ui-datepicker'); wp_enqueue_script('jquery-ui-dialog'); // Add jQuery UI Dialog // Load Leaflet for premium users on Activity Log page only $is_premium = !ACTLOGPRO_Premium_Helper::show_premium_ui(); $is_activity_log_page = (strpos($hook, 'activity-log-pro') !== false && !strpos($hook, 'activity-log-pro-')); if ($is_premium && $is_activity_log_page) { // Enqueue Leaflet CSS wp_enqueue_style('leaflet-css', ACTLOGPRO_PLUGIN_URL . 'assets/css/leaflet.css', array(), '1.9.4'); // Enqueue Leaflet fixes CSS with proper dependency wp_enqueue_style('alp-leaflet-fixes', ACTLOGPRO_PLUGIN_URL . 'admin/css/alp-leaflet-fixes.css', array('leaflet-css'), ACTLOGPRO_VERSION); // Set CSS custom properties for dynamic URLs with proper escaping $leaflet_vars_css = sprintf( ':root { --alp-leaflet-layers-icon: url(%s); --alp-leaflet-layers-retina-icon: url(%s); --alp-leaflet-marker-icon: url(%s); }', esc_url(ACTLOGPRO_PLUGIN_URL . 'assets/images/leaflet/layers.png'), esc_url(ACTLOGPRO_PLUGIN_URL . 'assets/images/leaflet/layers-2x.png'), esc_url(ACTLOGPRO_PLUGIN_URL . 'assets/images/leaflet/marker-icon.png') ); wp_add_inline_style('alp-leaflet-fixes', $leaflet_vars_css); // Enqueue our custom map styles wp_enqueue_style('alp-map-css', ACTLOGPRO_PLUGIN_URL . 'admin/css/alp-map.css', array('alp-leaflet-fixes'), ACTLOGPRO_VERSION); // Enqueue Leaflet JavaScript wp_enqueue_script('leaflet-js', ACTLOGPRO_PLUGIN_URL . 'assets/js/leaflet.js', array(), '1.9.4', true); } // Enqueue admin script wp_enqueue_script('alp-admin-js', ACTLOGPRO_PLUGIN_URL . 'admin/js/alp-admin.js', array('jquery', 'jquery-ui-datepicker', 'jquery-ui-dialog', 'wp-i18n'), ACTLOGPRO_VERSION, true); // Enqueue Quick Tips module on main activity log page only $is_main_activity_page = (strpos($hook, 'activity-log-pro') !== false && strpos($hook, 'activity-log-pro-') === false); if ($is_main_activity_page) { wp_enqueue_script('alp-quick-tips-js', ACTLOGPRO_PLUGIN_URL . 'admin/js/modules/alp-quick-tips.js', array('jquery'), ACTLOGPRO_VERSION, true); // Enqueue live monitor script for main activity log page wp_enqueue_script('alp-live-monitor-js', ACTLOGPRO_PLUGIN_URL . 'admin/js/modules/alp-live-monitor.js', array('jquery'), ACTLOGPRO_VERSION, true); // Localize Quick Tips script with AJAX data wp_localize_script('alp-quick-tips-js', 'actlogpro_ajax', array( 'ajaxurl' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('actlogpro_ajax_nonce') )); } // Register ES modules handler that loads other modules // Don't use type="module" directly, load ES modules with a proper fallback wp_register_script('alp-modules-handler', ACTLOGPRO_PLUGIN_URL . 'admin/js/modules-handler.js', array('jquery'), ACTLOGPRO_VERSION, true); wp_enqueue_script('alp-modules-handler'); // Add a proper inline script to initialize modules once the page is loaded $modules_init = " document.addEventListener('DOMContentLoaded', function() { // Initialize any modules needed for the current page if (typeof window.ACTLOGPRO_ModulesHandler !== 'undefined') { window.ACTLOGPRO_ModulesHandler.init(); } }); "; wp_add_inline_script('alp-modules-handler', $modules_init); // Get the items per page setting $items_per_page = get_option('actlogpro_items_per_page', 10); // Get excluded option names for JavaScript $excluded_options_string = get_option('actlogpro_excluded_option_names', ''); $excluded_options_array = array_filter(array_map('trim', explode("\n", $excluded_options_string))); // Prepare admin data for localization $admin_data = array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('actlogpro_ajax_nonce'), 'error_text' => __('An error occurred. Please try again.', 'activity-log-pro'), 'no_logs_text' => __('No logs found.', 'activity-log-pro'), 'confirm_clear_text' => __('Are you sure you want to clear all logs? This action cannot be undone.', 'activity-log-pro'), 'items_per_page' => $items_per_page, 'allow_multiple_expanded' => get_option('actlogpro_allow_multiple_expanded_rows', '0') === '1', 'admin_url' => admin_url(), 'upgrade_url' => ACTLOGPRO_Premium_Helper::get_upgrade_url(), 'plugin_version' => ACTLOGPRO_VERSION, 'i18n' => array( 'save_to_see_url' => __('Save settings and refresh the page to see the JSON feed URL.', 'activity-log-pro'), 'save_to_see_rss_url' => __('Save settings and refresh the page to see the RSS feed URL.', 'activity-log-pro') ), 'confirmResetSettings' => __('Are you sure you want to reset all tracking settings to default values?', 'activity-log-pro'), 'settingsSaved' => __('Settings saved successfully.', 'activity-log-pro'), 'tokenGenerated' => __('Feed token generated successfully.', 'activity-log-pro'), 'settingsReset' => __('Tracking settings have been reset to defaults.', 'activity-log-pro'), 'plugin_url' => esc_url(ACTLOGPRO_PLUGIN_URL), 'is_premium' => $is_premium, 'has_leaflet' => ($is_premium && $is_activity_log_page), // Add this for JavaScript 'leaflet_icon_urls' => array( // Add icon URLs for JavaScript 'iconUrl' => esc_url(ACTLOGPRO_PLUGIN_URL . 'assets/images/leaflet/marker-icon.png'), 'iconRetinaUrl' => esc_url(ACTLOGPRO_PLUGIN_URL . 'assets/images/leaflet/marker-icon-2x.png'), 'shadowUrl' => esc_url(ACTLOGPRO_PLUGIN_URL . 'assets/images/leaflet/marker-shadow.png') ), 'excluded_options' => $excluded_options_array, 'day_divider_today' => __('Today', 'activity-log-pro'), 'day_divider_yesterday' => __('Yesterday', 'activity-log-pro'), 'day_divider_separator' => _x(' · ', 'Between relative day label and calendar date in log divider.', 'activity-log-pro'), // translators: %d: number of days (2–5) for the activity log day divider. 'day_divider_days_ago' => __( '%d days ago', 'activity-log-pro' ), ); // Localize scripts wp_localize_script('alp-admin-js', 'actlogpro_ajax_object', $admin_data); wp_localize_script('alp-modules-handler', 'actlogproModulesData', $admin_data); // Set up translations if (function_exists('wp_set_script_translations')) { wp_set_script_translations('alp-admin-js', 'activity-log-pro'); } // Load integrations scripts on the integrations page if (strpos($hook, 'activity-log-pro-integrations') !== false) { wp_enqueue_style('alp-integrations-css', ACTLOGPRO_PLUGIN_URL . 'admin/css/alp-integrations.css', array('alp-admin-css'), ACTLOGPRO_VERSION); wp_enqueue_script('alp-integrations-js', ACTLOGPRO_PLUGIN_URL . 'admin/js/alp-integrations.js', array('jquery'), ACTLOGPRO_VERSION, true); // Localize integrations script wp_localize_script('alp-integrations-js', 'actlogpro_integrations_object', array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('actlogpro_ajax_nonce') )); } // Load Chart.js and stats-specific assets on the stats & insights page if (strpos($hook, 'activity-log-pro-stats') !== false) { // Enqueue stats page CSS wp_enqueue_style('alp-stats-css', ACTLOGPRO_PLUGIN_URL . 'admin/css/alp-stats.css', array('alp-admin-css'), ACTLOGPRO_VERSION); // Enqueue Chart.js wp_enqueue_script('chart-js', ACTLOGPRO_PLUGIN_URL . 'admin/js/chart.min.js', array(), '4.5.1', true); // Enqueue stats page JavaScript wp_enqueue_script('alp-stats-js', ACTLOGPRO_PLUGIN_URL . 'admin/js/alp-stats.js', array('jquery', 'chart-js'), ACTLOGPRO_VERSION, true); // Localize script specifically for stats page wp_localize_script('alp-stats-js', 'actlogproAjax', array( 'ajaxurl' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('actlogpro_ajax_nonce') )); // Localize stats data for charts $this->localize_stats_data(); } // Load upgrade-specific assets on the upgrade page if (strpos($hook, 'activity-log-pro-upgrade') !== false) { // Enqueue upgrade page CSS wp_enqueue_style('alp-upgrade-css', ACTLOGPRO_PLUGIN_URL . 'admin/css/alp-upgrade.css', array('alp-admin-css'), ACTLOGPRO_VERSION); // Enqueue upgrade page JavaScript wp_enqueue_script('alp-upgrade-js', ACTLOGPRO_PLUGIN_URL . 'admin/js/alp-upgrade.js', array('jquery'), ACTLOGPRO_VERSION, true); // Enqueue LemonSqueezy checkout script wp_enqueue_script( 'lemonsqueezy-checkout', 'https://assets.lemonsqueezy.com/lemon.js', array(), '1.0.0', true ); // Add defer attribute to LemonSqueezy script add_filter('script_loader_tag', array($this, 'add_defer_to_lemonsqueezy'), 10, 2); } // Load newsletter script and additional styles on the settings page if (strpos($hook, 'activity-log-pro-settings') !== false) { // Enqueue additional settings styles to replace inline styles wp_enqueue_style( 'alp-settings-additional-css', ACTLOGPRO_PLUGIN_URL . 'admin/css/alp-settings-additional.css', array('alp-admin-css'), ACTLOGPRO_VERSION ); wp_enqueue_script( 'alp-newsletter', ACTLOGPRO_PLUGIN_URL . 'admin/js/alp-newsletter.js', array('jquery'), ACTLOGPRO_VERSION, true ); } // Add datepicker initialization for export page if (strpos($hook, 'activity-log-pro-export') !== false) { $datepicker_script = " jQuery(document).ready(function($) { // Initialize datepickers $('.alp-datepicker').datepicker({ dateFormat: 'yy-mm-dd', changeMonth: true, changeYear: true }); }); "; wp_add_inline_script('jquery-ui-datepicker', $datepicker_script); } } /** * Display the plugin logo. * * @since 1.0.0 */ private function display_plugin_logo() { // Register the logo image $logo_url = ACTLOGPRO_PLUGIN_URL . 'assets/images/alp-logo-final.png'; $admin_url = admin_url('admin.php?page=activity-log-pro'); // Logo styling is now handled in alp-admin.css // Start the header section container echo '
'; // Generate the logo HTML using wp_get_attachment_image if possible echo ''; // Close the header section container echo '
'; } /** * Display plugin logo fallback when the image is not in the media library. * This uses WordPress's recommended image handling approach. * * @since 1.0.0 * @param string $logo_url URL of the logo image. */ private function display_plugin_logo_fallback($logo_url) { // Get image dimensions $width = 250; // Default width $height = 80; // Default height // Try to get actual dimensions using WordPress functions $image_path = str_replace(ACTLOGPRO_PLUGIN_URL, ACTLOGPRO_PLUGIN_DIR, $logo_url); if (file_exists($image_path)) { $image_size = @getimagesize($image_path); if ($image_size) { list($width, $height) = $image_size; } } // Even though we don't have a real attachment ID, we can simulate the attributes $srcset = "$logo_url {$width}w"; $sizes = "(max-width: {$width}px) 100vw, {$width}px"; // Create image HTML properly using WordPress standards $attrs = array( 'src' => $logo_url, 'alt' => 'Activity Log Pro', 'class' => 'alp-logo-image wp-image-0 attachment-medium size-medium', 'width' => $width, 'height' => $height, 'srcset' => $srcset, 'sizes' => $sizes, 'loading' => 'lazy' ); // Build attributes string for wp_kses $attr_html = ''; foreach ($attrs as $name => $value) { $attr_html .= ' ' . esc_attr($name) . '="' . esc_attr($value) . '"'; } // Define allowed HTML elements and attributes $allowed_html = array( 'img' => array( 'src' => true, 'srcset' => true, 'sizes' => true, 'alt' => true, 'class' => true, 'width' => true, 'height' => true, 'loading' => true, ), ); // Output the image using wp_kses to ensure security echo wp_kses('', $allowed_html); } /** * Display plugin image using WordPress standards. * * @since 1.0.0 * @param string $image_name Image filename in assets/images/ * @param string $alt_text Alt text for the image * @param string $css_class CSS class for the image * @param array $attributes Additional attributes * @return string HTML for the image */ private function display_plugin_image($image_name, $alt_text = '', $css_class = '', $attributes = array()) { $image_url = ACTLOGPRO_PLUGIN_URL . 'assets/images/' . $image_name; // Check if we have an attachment ID for the image $attachment_id = attachment_url_to_postid($image_url); if ($attachment_id) { // Use wp_get_attachment_image if we have an attachment ID $default_attributes = array( 'alt' => $alt_text, 'class' => $css_class ); $final_attributes = array_merge($default_attributes, $attributes); return wp_get_attachment_image($attachment_id, 'medium', false, $final_attributes); } else { // Use fallback method for plugin assets return $this->display_plugin_image_fallback($image_url, $alt_text, $css_class, $attributes); } } /** * Display plugin image fallback when not in media library. * * @since 1.0.0 * @param string $image_url URL of the image * @param string $alt_text Alt text for the image * @param string $css_class CSS class for the image * @param array $attributes Additional attributes * @return string HTML for the image */ private function display_plugin_image_fallback($image_url, $alt_text = '', $css_class = '', $attributes = array()) { // Get image dimensions $width = 250; // Default width $height = 80; // Default height // Try to get actual dimensions $image_path = str_replace(ACTLOGPRO_PLUGIN_URL, ACTLOGPRO_PLUGIN_DIR, $image_url); if (file_exists($image_path)) { $image_size = @getimagesize($image_path); if ($image_size) { list($width, $height) = $image_size; } } // Build default attributes $default_attrs = array( 'src' => $image_url, 'alt' => $alt_text, 'class' => $css_class, 'width' => $width, 'height' => $height, 'srcset' => "$image_url {$width}w", 'sizes' => "(max-width: {$width}px) 100vw, {$width}px", 'loading' => 'lazy' ); // Merge with custom attributes $attrs = array_merge($default_attrs, $attributes); // Build attributes string $attr_html = ''; foreach ($attrs as $name => $value) { if (!empty($value)) { $attr_html .= ' ' . esc_attr($name) . '="' . esc_attr($value) . '"'; } } // Define allowed HTML $allowed_html = array( 'img' => array( 'src' => true, 'srcset' => true, 'sizes' => true, 'alt' => true, 'class' => true, 'width' => true, 'height' => true, 'loading' => true, ), ); return wp_kses('', $allowed_html); } /** * Display the activity log page. * * @since 1.0.0 */ public function display_activity_log_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(); // Check if horizontal tabs are active $menu_location = get_option('actlogpro_admin_menu_location', 'default'); $tabs_active = in_array($menu_location, array('dashboard', 'tools')); echo '
'; // Add page title if horizontal tabs are active if ($tabs_active) { echo '

'; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo esc_html(__('Activity Logs', 'activity-log-pro')) . '

'; echo '
'; } // Include the main activity log display template include_once ACTLOGPRO_PLUGIN_DIR . 'admin/partials/activity-log-display.php'; echo '
'; } /** * 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 '

'; echo ''; echo ''; echo ''; echo ''; echo esc_html(__('Settings', 'activity-log-pro')) . '

'; 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'])) : ''; // 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', '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 ); // 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 = !empty($log->metadata) ? maybe_unserialize($log->metadata) : array(); // 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'; } } $formatted_logs[] = array( 'id' => $log->id, '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' => maybe_unserialize($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'))); } } /** * 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'] = maybe_unserialize($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 = !empty($log['metadata']) ? maybe_unserialize($log['metadata']) : array(); return [ 'id' => $log['id'], '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_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(); } // 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 )); } /** * 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 '

'; echo ''; echo ''; echo ''; echo ''; echo ''; echo esc_html(__('Export', 'activity-log-pro')) . '

'; 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 '

'; echo ''; echo ''; echo ''; echo ''; echo esc_html(__('Integrations', 'activity-log-pro')) . '

'; 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 '

'; echo ''; echo ''; echo ''; echo ''; echo ''; echo esc_html(__('Stats & Insights', 'activity-log-pro')) . '

'; 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 ''; 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 '
'; foreach ($tabs as $slug => $tab) { $active_class = ($current_page === $slug) ? ' alp-tab-active' : ''; echo ''; echo ''; echo '' . esc_html($tab['title']) . ''; echo ''; } echo '
'; } /** * AJAX handler for IP geolocation lookup using ipinfo.io * * @since 1.0.0 */ public function ajax_get_ip_info() { // Check nonce for security 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; } // Get and validate IP address $ip_address = isset($_POST['ip_address']) ? sanitize_text_field(wp_unslash($_POST['ip_address'])) : ''; if (empty($ip_address)) { wp_send_json_error(array('message' => __('IP address is required.', 'activity-log-pro'))); return; } // Validate IP address format if (!filter_var($ip_address, FILTER_VALIDATE_IP)) { wp_send_json_error(array('message' => __('Invalid IP address format.', 'activity-log-pro'))); return; } // Check cache first (cache for 24 hours) $cache_key = 'actlogpro_ip_info_' . md5($ip_address); $cached_data = get_transient($cache_key); if ($cached_data !== false) { wp_send_json_success(array( 'ip_data' => $cached_data, 'cached' => true )); return; } // Make request to ipinfo.io API $api_url = 'https://ipinfo.io/' . $ip_address . '/json'; $response = wp_remote_get($api_url, array( 'timeout' => 10, 'user-agent' => 'Activity Log Pro WordPress Plugin', 'headers' => array( 'Accept' => 'application/json' ) )); // Check for errors if (is_wp_error($response)) { wp_send_json_error(array( 'message' => __('Failed to retrieve IP information: ', 'activity-log-pro') . $response->get_error_message() )); return; } $response_code = wp_remote_retrieve_response_code($response); if ($response_code !== 200) { wp_send_json_error(array( 'message' => __('API request failed with status code: ', 'activity-log-pro') . $response_code )); return; } $body = wp_remote_retrieve_body($response); $ip_data = json_decode($body, true); if (json_last_error() !== JSON_ERROR_NONE) { wp_send_json_error(array( 'message' => __('Failed to parse API response.', 'activity-log-pro') )); return; } // Sanitize the response data $sanitized_data = array( 'ip' => isset($ip_data['ip']) ? sanitize_text_field($ip_data['ip']) : '', 'hostname' => isset($ip_data['hostname']) ? sanitize_text_field($ip_data['hostname']) : '', 'city' => isset($ip_data['city']) ? sanitize_text_field($ip_data['city']) : '', 'region' => isset($ip_data['region']) ? sanitize_text_field($ip_data['region']) : '', 'country' => isset($ip_data['country']) ? sanitize_text_field($ip_data['country']) : '', 'loc' => isset($ip_data['loc']) ? sanitize_text_field($ip_data['loc']) : '', 'org' => isset($ip_data['org']) ? sanitize_text_field($ip_data['org']) : '', 'postal' => isset($ip_data['postal']) ? sanitize_text_field($ip_data['postal']) : '', 'timezone' => isset($ip_data['timezone']) ? sanitize_text_field($ip_data['timezone']) : '' ); // Cache the result for 24 hours set_transient($cache_key, $sanitized_data, DAY_IN_SECONDS); // Return success response wp_send_json_success(array( 'ip_data' => $sanitized_data, 'cached' => false )); } /** * Display admin notice when plugin is activated * * @since 1.0.0 */ public function display_activation_notice() { // Check if we should show the activation notice if (!get_option('actlogpro_show_activation_notice', false)) { return; } // 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-activation-notice'])) { // User has dismissed it, but we still need to clear the global flag delete_option('actlogpro_show_activation_notice'); return; } // Only show to users who can manage options if (!current_user_can('manage_options')) { return; } // Get plugin version $version = ACTLOGPRO_VERSION; // Build settings and help URLs $settings_url = admin_url('admin.php?page=activity-log-pro-settings'); $help_url = admin_url('admin.php?page=activity-log-pro-settings&tab=help'); echo '
'; echo '

' . esc_html__('Thanks for installing Activity Log Pro v', 'activity-log-pro') . esc_html($version) . esc_html__(' - Enjoy the plugin - Activity Log Pro Team', 'activity-log-pro') . '

'; echo '

' . esc_html__('Check: ', 'activity-log-pro'); echo '' . esc_html__('Plugin Settings', 'activity-log-pro') . ' | '; echo '' . esc_html__('Documentation', 'activity-log-pro') . ''; echo '

'; 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() { // Check if premium settings have been reset if (get_option('actlogpro_premium_settings_reset_notice', false)) { $message = __('Activity Log Pro - Premium features have been disabled and settings have been reset to default values.', '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 '
'; echo '

' . esc_html__('Activity Log Pro - Caching Plugin Detected', 'activity-log-pro') . '

'; // 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() { ?>