[]]; } foreach ($notifications as $nkey => $settings) { // skip if "enable note" is not set to 1 if ( ! isset( $settings['atk_enable_note'] ) || intval( $settings['atk_enable_note'] ) !== 1 ) { continue; } // Check email limit $emailCountKey = "atk_email_count_{$activityType}_{$status}_{$userId}"; $emailCount = get_option($emailCountKey, 0); if( !empty( $settings['atk_email_limit'] ) && intval($emailCount) >= intval($settings['atk_email_limit']) ) { continue; } if( !empty($settings['atk_user']) && $userId ) { $get_user_ID = ( is_string($settings['atk_user'] ) ) ? $this->get_user_id($settings['atk_user']) : $settings['atk_user']; if( $userId != $get_user_ID ) { continue; } } if (isset($settings['activity_types']) && $settings['activity_types'] === $activityType && isset($settings['activity_status']) && $settings['activity_status'] === $status) { $to_raw = $settings['atk_email_to'] ?? ''; $to = sanitize_email( $to_raw ); if ( $this->is_valid_email( $to ) ) { $subject = sanitize_text_field( "Activity Notification: {$activityType}" ); $username = esc_html( $this->get_username( $userId ) ); $message = "

An activity of type {$activityType} occurred with status {$status} for user {$username}.

\nPowered by Activity Track AI"; $headers = array( 'Content-Type: text/html; charset=UTF-8' ); // prevent duplicate notifications within an hour $sent_key = 'atk_email_sent_' . md5( "{$activityType}|{$status}|{$userId}|{$to}" ); if ( false === get_transient( $sent_key ) ) { wp_mail( $to, $subject, $message, $headers ); set_transient( $sent_key, true, 60 ); // record and count only on first send $this->add_update_records_stats( $activityType, $status, $userId, $to ); update_option( $emailCountKey, ++$emailCount, false ); } else { // skipped duplicate notification } } else { $to = 'No email sent'; $this->add_update_records_stats( $activityType, $status, $userId, $to ); } } } } /** * Get user ID from username. * * @param int|string $userId The user ID or username. * @return int The user ID. */ public function get_user_id($userId) { if (is_numeric($userId)) { return $userId; } else { $user = get_user_by('login', $userId); return $user ? $user->ID : 0; } } /** * Get user ID from username. * * @param int|string $userId The user ID or username. * @return string The username. */ public function get_username( $user_id ) { $user_info = get_userdata($user_id); if ($user_info) { $username = $user_info->user_login; return $username; } else { return 'unknown'; } } private static function is_valid_email( $raw_email ) { // 1) Reverse magic-quotes slashes and sanitize $email = sanitize_email( wp_unslash( $raw_email ) ); // 2) Let WP validate (returns the sanitized email, or false on failure) return false !== is_email( $email ); } // Function to get all records sorted by created_date public function get_all_records_stats() { global $wpdb; $results = $wpdb->get_results( $wpdb->prepare( // we add "WHERE 1 = %d" purely to give prepare() a real placeholder 'SELECT * FROM `' . esc_sql( $wpdb->prefix . 'atk_ai_stats' ) . '` WHERE 1 = %d ORDER BY updated_date DESC', 1 ) ); // Return rows or false return ! empty( $results ) ? $results : false; } // Add/update record public function add_update_records_stats( $action, $action_name, $user_id, $email_to ) { global $wpdb; $activity_types = include plugin_dir_path( ATKAI_PLUGIN_FILE ) . '/activities/types.php'; // Sanitize inputs $action = esc_sql($action); $action_name = esc_sql($action_name); $user_id = intval($user_id); $email_to = esc_sql($email_to); $action_name = $activity_types[$action][$action_name] ?? ''; $existing_record = $wpdb->get_row( $wpdb->prepare( // Inline-escape the table name and concatenate it outside the quoted string 'SELECT * FROM `' . esc_sql( $wpdb->prefix . 'atk_ai_stats' ) . '` WHERE action = %s AND action_name = %s AND user_id = %d AND email_to = %s', $action, $action_name, $user_id, $email_to ) ); if ($existing_record) { // Record exists, update the count $wpdb->update( "{$wpdb->prefix}atk_ai_stats", array('count_emails' => $existing_record->count_emails + 1, 'updated_date' => current_time( 'mysql', 1 ) ), array('stat_id' => $existing_record->stat_id) ); } else { // Record does not exist, insert a new one $wpdb->insert( "{$wpdb->prefix}atk_ai_stats", array( 'action' => $action, 'action_name' => $action_name, 'user_id' => $user_id, 'email_to' => $email_to, 'count_emails' => 1, 'created_date' => current_time( 'mysql', 1 ), 'updated_date' => current_time( 'mysql', 1 ) ) ); } } public function atk_delete_all_reports_callback() { global $wpdb; if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( [ 'message' => 'You cannot delete activity reports.' ], 403 ); } // 1) Pull & sanitize the incoming nonce $nonce = sanitize_text_field( wp_unslash( $_POST['nonce'] ?? '' ) ); // 2) Verify it if ( ! wp_verify_nonce( $nonce, 'atk_del_all_reports' ) ) { wp_send_json_error( [ 'message' => 'Nonce verification failed' ], 200 ); } // 3) Escape the full table name $table = esc_sql( $wpdb->prefix . self::ATKAI_REPORT_TABLE ); /** * 4) Inline-prepare your static TRUNCATE to satisfy PHPCS. * No user data is interpolated into the SQL itself. */ $result = $wpdb->query( sprintf( 'TRUNCATE TABLE `%s`', esc_sql( $wpdb->prefix . 'atk_ai_stats' ) ) ); if ( false === $result ) { wp_send_json_error( [ 'message' => 'There was an error deleting the records' ], 200 ); } wp_send_json_success( [ 'message' => 'All reports have been deleted successfully' ], 200 ); } } new Atk_Admin_Notifications();