array(), 'request_count' => 0, 'dismissed_type' => null, 'next_reminder' => null, 'install_date' => current_time('timestamp'), 'pending_notification' => null ); $data = get_option(self::$option_name, $defaults); // Ensure all keys exist and are properly typed $data = wp_parse_args($data, $defaults); $data['milestones_reached'] = is_array($data['milestones_reached']) ? $data['milestones_reached'] : array(); $data['request_count'] = absint($data['request_count']); $data['install_date'] = absint($data['install_date']); $data['pending_notification'] = !empty($data['pending_notification']) ? $data['pending_notification'] : null; return $data; } /** * Update rating request data in options table. * * @since 1.0.3 * @param array $data Rating request data to save. * @return bool True if successful, false otherwise. */ private static function actlogpro_update_rating_data($data) { // Sanitize data before saving $sanitized_data = array( 'milestones_reached' => is_array($data['milestones_reached']) ? array_map('sanitize_text_field', $data['milestones_reached']) : array(), 'request_count' => absint($data['request_count']), 'dismissed_type' => !empty($data['dismissed_type']) ? sanitize_text_field($data['dismissed_type']) : null, 'next_reminder' => !empty($data['next_reminder']) ? absint($data['next_reminder']) : null, 'install_date' => absint($data['install_date']), 'pending_notification' => !empty($data['pending_notification']) ? $data['pending_notification'] : null ); return update_option(self::$option_name, $sanitized_data); } /** * Check if any milestones have been reached. * * @since 1.0.3 * @return array|false Milestone data if reached, false otherwise. */ private static function actlogpro_check_milestones() { $rating_data = self::actlogpro_get_rating_data(); // Don't show if permanently dismissed or max requests reached if ($rating_data['dismissed_type'] === 'done' || $rating_data['dismissed_type'] === 'never' || $rating_data['request_count'] >= self::$max_requests) { return false; } // Only block if we're in a short delay period (within 2 days) to allow new milestones // Long delays from "maybe later" (30 days) shouldn't block new milestone types if ($rating_data['next_reminder'] && current_time('timestamp') < $rating_data['next_reminder']) { $delay_remaining = $rating_data['next_reminder'] - current_time('timestamp'); // Only block if delay is less than 3 days (allows new milestones after short delays) if ($delay_remaining < (3 * DAY_IN_SECONDS)) { return false; } } // Check each milestone type $milestones = array( 'logs' => self::actlogpro_check_log_milestones($rating_data), 'time' => self::actlogpro_check_time_milestones($rating_data), 'security' => self::actlogpro_check_security_milestones($rating_data), 'changes' => self::actlogpro_check_change_milestones($rating_data) ); // Return first milestone found foreach ($milestones as $type => $milestone) { if ($milestone) { return $milestone; } } return false; } /** * Check log count milestones using existing database method. * * @since 1.0.3 * @param array $rating_data Current rating request data. * @return array|false Milestone data if reached, false otherwise. */ private static function actlogpro_check_log_milestones($rating_data) { if (!class_exists('ACTLOGPRO_Database')) { return false; } $total_logs = ACTLOGPRO_Database::get_total_logs_count(); $thresholds = array(100, 500, 1000, 5000); foreach ($thresholds as $threshold) { $milestone_key = "logs_{$threshold}"; if ($total_logs >= $threshold && !in_array($milestone_key, $rating_data['milestones_reached'], true)) { return array( 'type' => 'logs', 'key' => $milestone_key, 'count' => $total_logs, 'threshold' => $threshold, 'message' => sprintf( /* translators: %s: Number of activities tracked */ esc_html__('Congratulations! You\'ve successfully tracked %s activities across your site. Your comprehensive security monitoring is helping protect your WordPress site.', 'activity-log-pro'), number_format_i18n($total_logs) ) ); } } return false; } /** * Check time-based milestones. * * @since 1.0.3 * @param array $rating_data Current rating request data. * @return array|false Milestone data if reached, false otherwise. */ private static function actlogpro_check_time_milestones($rating_data) { $install_date = $rating_data['install_date']; $days_since_install = floor((current_time('timestamp') - $install_date) / DAY_IN_SECONDS); $thresholds = array(7, 30, 90); foreach ($thresholds as $threshold) { $milestone_key = "time_{$threshold}days"; if ($days_since_install >= $threshold && !in_array($milestone_key, $rating_data['milestones_reached'], true)) { return array( 'type' => 'time', 'key' => $milestone_key, 'days' => $days_since_install, 'threshold' => $threshold, 'message' => sprintf( /* translators: %s: Number of days */ esc_html__('Congratulations! You\'ve been using Activity Log Pro for %s days. Your ongoing security monitoring is keeping your WordPress site protected.', 'activity-log-pro'), number_format_i18n($days_since_install) ) ); } } return false; } /** * Check security event milestones using existing database method. * * @since 1.0.3 * @param array $rating_data Current rating request data. * @return array|false Milestone data if reached, false otherwise. */ private static function actlogpro_check_security_milestones($rating_data) { if (!class_exists('ACTLOGPRO_Database')) { return false; } // Get all-time failed logins count (from plugin install to now) $earliest_date = wp_date('Y-m-d', $rating_data['install_date']); $today = current_time('Y-m-d'); $failed_logins = ACTLOGPRO_Database::get_failed_logins_count_by_date_range($earliest_date, $today); $threshold = 5; $milestone_key = "security_failed_logins_{$threshold}"; if ($failed_logins >= $threshold && !in_array($milestone_key, $rating_data['milestones_reached'], true)) { return array( 'type' => 'security', 'key' => $milestone_key, 'count' => $failed_logins, 'threshold' => $threshold, 'message' => sprintf( /* translators: %s: Number of failed login attempts */ esc_html__('Great security monitoring! Activity Log Pro has detected %s failed login attempts, helping you identify potential security threats to your WordPress site.', 'activity-log-pro'), number_format_i18n($failed_logins) ) ); } return false; } /** * Check plugin/theme/option change milestones. * * @since 1.0.3 * @param array $rating_data Current rating request data. * @return array|false Milestone data if reached, false otherwise. */ private static function actlogpro_check_change_milestones($rating_data) { if (!class_exists('ACTLOGPRO_Database')) { return false; } // Get business stats for changes (approximate count) $today = current_time('Y-m-d'); $install_date = wp_date('Y-m-d', $rating_data['install_date']); // Get cumulative stats since install (this is an approximation) $days_since_install = max(1, floor((current_time('timestamp') - $rating_data['install_date']) / DAY_IN_SECONDS)); $daily_stats = ACTLOGPRO_Database::get_business_daily_stats($today); // Rough estimate of total changes (not perfect but gives indication) $estimated_changes = ($daily_stats['plugin_theme_activities'] + $daily_stats['admin_changes']) * min($days_since_install, 30); $threshold = 20; $milestone_key = "changes_{$threshold}"; if ($estimated_changes >= $threshold && !in_array($milestone_key, $rating_data['milestones_reached'], true)) { return array( 'type' => 'changes', 'key' => $milestone_key, 'count' => $estimated_changes, 'threshold' => $threshold, 'message' => sprintf( /* translators: %s: Number of plugin/theme/setting changes */ esc_html__('Excellent tracking! You\'ve logged %s+ plugin, theme, and setting changes. Activity Log Pro is providing comprehensive oversight of your WordPress site modifications.', 'activity-log-pro'), number_format_i18n($threshold) ) ); } return false; } /** * Display rating request admin notice. * * @since 1.0.3 */ public static function display_rating_request() { $rating_data = self::actlogpro_get_rating_data(); $milestone = null; // First check if there's a pending notification to show if (!empty($rating_data['pending_notification'])) { $milestone = $rating_data['pending_notification']; } else { // Only check for new milestones occasionally (every 5th page load) if (self::should_check_milestones()) { $milestone = self::actlogpro_check_milestones(); if ($milestone) { // Save as pending notification (don't mark as reached yet) $rating_data['pending_notification'] = $milestone; self::actlogpro_update_rating_data($rating_data); } } } if (!$milestone) { return; } // Generate nonce for AJAX actions $nonce = wp_create_nonce('actlogpro_rating_request_nonce'); ?>
__('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; } // Validate and sanitize action if (!isset($_POST['action_type']) || empty($_POST['action_type'])) { wp_send_json_error(array('message' => __('Action type is required.', 'activity-log-pro'))); return; } $action_type = sanitize_text_field(wp_unslash($_POST['action_type'])); $allowed_actions = array('review', 'later', 'done', 'close'); if (!in_array($action_type, $allowed_actions, true)) { wp_send_json_error(array('message' => __('Invalid action type.', 'activity-log-pro'))); return; } $rating_data = self::actlogpro_get_rating_data(); // Mark the pending notification as reached and increment request count if (!empty($rating_data['pending_notification'])) { $milestone_key = $rating_data['pending_notification']['key']; if (!in_array($milestone_key, $rating_data['milestones_reached'], true)) { $rating_data['milestones_reached'][] = $milestone_key; $rating_data['request_count']++; } // Clear the pending notification $rating_data['pending_notification'] = null; } switch ($action_type) { case 'review': // User clicked to review - mark as done $rating_data['dismissed_type'] = 'done'; break; case 'later': // User wants to be reminded later (30 days) $rating_data['next_reminder'] = current_time('timestamp') + (30 * DAY_IN_SECONDS); break; case 'done': // User already reviewed - permanently dismiss $rating_data['dismissed_type'] = 'done'; break; case 'close': // Soft dismissal - hide for 7 days $rating_data['next_reminder'] = current_time('timestamp') + (7 * DAY_IN_SECONDS); break; } // Save updated data if (self::actlogpro_update_rating_data($rating_data)) { wp_send_json_success(array('message' => __('Rating request dismissed successfully.', 'activity-log-pro'))); } else { wp_send_json_error(array('message' => __('Failed to update rating request data.', 'activity-log-pro'))); } } /** * Development helper to reset rating request data for testing. * Only works if WP_DEBUG is enabled. * * @since 1.0.3 * @return bool True if reset successful, false otherwise. */ public static function actlogpro_reset_for_testing() { // Only allow in development environment if (!defined('WP_DEBUG') || !WP_DEBUG) { return false; } // Only allow for administrators if (!current_user_can('manage_options')) { return false; } // Reset rating request data delete_option(self::$option_name); delete_transient('actlogpro_milestone_check_counter'); return true; } /** * Development helper to set a pending notification for testing. * Only works if WP_DEBUG is enabled. * * @since 1.0.3 * @param array $milestone_data Milestone data to set as pending. * @return bool True if set successfully, false otherwise. */ public static function actlogpro_set_pending_notification_for_testing($milestone_data) { // Only allow in development environment if (!defined('WP_DEBUG') || !WP_DEBUG) { return false; } // Only allow for administrators if (!current_user_can('manage_options')) { return false; } if (empty($milestone_data) || !is_array($milestone_data)) { return false; } $rating_data = self::actlogpro_get_rating_data(); // Clear any existing dismissals to allow showing $rating_data['dismissed_type'] = null; $rating_data['next_reminder'] = null; $rating_data['request_count'] = 0; $rating_data['milestones_reached'] = array(); $rating_data['pending_notification'] = $milestone_data; return self::actlogpro_update_rating_data($rating_data); } }