esc_html(__('You do not have permission to perform this action.', 'ai-product-assistant')))); } $field_getter = ($field_type === 'short_description') ? 'get_short_description' : 'get_description'; $args = array( 'limit' => -1, 'status' => 'publish', ); $products = wc_get_products($args); if (empty($products)) { wp_send_json_error(array('message' => esc_html(__('No products found.', 'ai-product-assistant')))); } $total_count = count($products); $empty_count = 0; $under_limit_count = 0; $over_limit_count = 0; $length_distribution = array( '0' => 0, '1-100' => 0, '101-250' => 0, '251-500' => 0, '501-1000' => 0, '1001-2000' => 0, '2001+' => 0 ); $max_length = 0; $min_length = PHP_INT_MAX; $total_length = 0; foreach ($products as $product) { $content = $product->$field_getter(); $length = mb_strlen($content); $total_length += $length; if ($length === 0) { $empty_count++; $length_distribution['0']++; } elseif ($length <= 100) { $length_distribution['1-100']++; } elseif ($length <= 250) { $length_distribution['101-250']++; } elseif ($length <= 500) { $length_distribution['251-500']++; } elseif ($length <= 1000) { $length_distribution['501-1000']++; } elseif ($length <= 2000) { $length_distribution['1001-2000']++; } else { $length_distribution['2001+']++; } if ($length <= $limit) { $under_limit_count++; } else { $over_limit_count++; } if ($length > $max_length) { $max_length = $length; } if ($length < $min_length && $length > 0) { $min_length = $length; } } $non_empty_count = $total_count - $empty_count; $avg_length = $non_empty_count > 0 ? round($total_length / $non_empty_count) : 0; if ($min_length === PHP_INT_MAX) { $min_length = 0; } $result = array( 'total_count' => $total_count, 'empty_count' => $empty_count, 'under_limit_count' => $under_limit_count, 'over_limit_count' => $over_limit_count, 'empty_percent' => round(($empty_count / $total_count) * 100, 1), 'under_limit_percent' => round(($under_limit_count / $total_count) * 100, 1), 'over_limit_percent' => round(($over_limit_count / $total_count) * 100, 1), 'max_length' => $max_length, 'min_length' => $min_length, 'avg_length' => $avg_length, 'length_distribution' => $length_distribution, 'current_limit' => $limit, 'field_type' => $field_type ); wp_send_json_success($result); } /** * AJAX analyze short description length */ public function ajax_analyze_short_description_length() { $_POST['field_type'] = 'short_description'; $this->ajax_analyze_description_length(); } /** * AJAX get prompt details */ public function ajax_get_prompt_details() { check_ajax_referer('aipa_admin_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error('Insufficient permissions.'); return; } $stat_id = isset($_POST['id']) ? intval($_POST['id']) : 0; if (empty($stat_id)) { wp_send_json_error(array('message' => esc_html(__('Invalid statistics ID.', 'ai-product-assistant')))); } global $wpdb; $table_name = $wpdb->prefix . 'aipa_statistics'; // Get statistics data with prepared statement for security // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching $stat = $wpdb->get_row($wpdb->prepare( "SELECT request_prompt, response_text FROM {$wpdb->prefix}aipa_statistics WHERE id = %d", $stat_id )); if (!$stat) { wp_send_json_error(array('message' => esc_html(__('Statistics not found.', 'ai-product-assistant')))); } wp_send_json_success(array( 'prompt' => $stat->request_prompt, 'response' => $stat->response_text )); } /** * AJAX Get Available Meta Keys */ public function ajax_get_available_meta_keys() { check_ajax_referer('aipa_admin_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(array('message' => esc_html(__('You do not have permission to perform this action.', 'ai-product-assistant')))); } global $wpdb; // Get all product meta keys for admin interface (no user input) // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching $all_meta_keys = $wpdb->get_col( "SELECT DISTINCT pm.meta_key FROM {$wpdb->postmeta} pm INNER JOIN {$wpdb->posts} p ON pm.post_id = p.ID WHERE p.post_type = 'product' ORDER BY pm.meta_key" ); $exclude_keys = array( '_edit_lock', '_edit_last', '_wp_page_template', '_wp_attachment_metadata', '_wp_attached_file' ); $filtered_meta_keys = array_diff($all_meta_keys, $exclude_keys); wp_send_json_success(array( 'meta_keys' => array_values($filtered_meta_keys), 'total_keys' => count($all_meta_keys), 'filtered_keys_count' => count($filtered_meta_keys) )); } /** * AJAX Save Custom Fields */ public function ajax_save_custom_fields() { check_ajax_referer('aipa_admin_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(array('message' => esc_html(__('You do not have permission to perform this action.', 'ai-product-assistant')))); } $options = get_option('aipa_settings'); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- JSON data is sanitized after decoding $custom_fields = isset($_POST['custom_fields']) ? json_decode(stripslashes(wp_unslash($_POST['custom_fields'])), true) : array(); $sanitized_fields = array(); if (is_array($custom_fields)) { foreach ($custom_fields as $field) { if (isset($field['meta_key']) && isset($field['description'])) { $sanitized_fields[] = array( // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key 'meta_key' => sanitize_key($field['meta_key']), 'description' => sanitize_text_field($field['description']) ); } } } $options['custom_fields'] = $sanitized_fields; update_option('aipa_settings', $options); wp_send_json_success(array( 'message' => __('Custom fields saved successfully.', 'ai-product-assistant'), 'custom_fields' => $sanitized_fields )); } /** * Display the statistics page */ public function display_statistics_page() { if (!current_user_can('manage_options')) { return; } // Safely get pagination parameter $current_page = 1; // Reading pagination parameter for display purposes only (no data modification) // phpcs:ignore WordPress.Security.NonceVerification.Recommended if (isset($_GET['paged'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $page = intval(wp_unslash($_GET['paged'])); if ($page > 0) { $current_page = $page; } } $per_page = 20; global $wpdb; $table_name = $wpdb->prefix . 'aipa_statistics'; // Safely get time range parameter $time_range = 'today'; // Reading filter parameter for display purposes only (no data modification) // phpcs:ignore WordPress.Security.NonceVerification.Recommended if (isset($_GET['time_range'])) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $range = sanitize_text_field(wp_unslash($_GET['time_range'])); // Validate against allowed time ranges $allowed_ranges = array('today', 'yesterday', '7days', '30days', 'all'); if (in_array($range, $allowed_ranges, true)) { $time_range = $range; } } $where_clause = ''; switch ($time_range) { case 'today': $where_clause = "WHERE DATE(timestamp) = CURDATE()"; break; case 'yesterday': $where_clause = "WHERE DATE(timestamp) = DATE_SUB(CURDATE(), INTERVAL 1 DAY)"; break; case '7days': $where_clause = "WHERE timestamp >= DATE_SUB(NOW(), INTERVAL 7 DAY)"; break; case '30days': $where_clause = "WHERE timestamp >= DATE_SUB(NOW(), INTERVAL 30 DAY)"; break; default: $where_clause = ""; } // Build COUNT query with predefined conditions (no user input) $count_sql = "SELECT COUNT(*) FROM {$wpdb->prefix}aipa_statistics " . $where_clause; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared $total_items = $wpdb->get_var($count_sql); $total_pages = ceil($total_items / $per_page); $current_page = min($current_page, max(1, $total_pages)); $offset = ($current_page - 1) * $per_page; // Build main query with predefined where clause and prepare only the LIMIT values $main_sql = "SELECT * FROM {$wpdb->prefix}aipa_statistics " . $where_clause . " ORDER BY timestamp DESC LIMIT %d, %d"; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared $stats = $wpdb->get_results($wpdb->prepare($main_sql, $offset, $per_page)); // Build summary query with predefined conditions (no user input) $summary_sql = "SELECT COUNT(*) as total_requests, SUM(request_tokens) as total_request_tokens, SUM(response_tokens) as total_response_tokens, SUM(request_tokens + response_tokens) as total_tokens, AVG(execution_time) as avg_execution_time, COUNT(DISTINCT user_role) as unique_users FROM {$wpdb->prefix}aipa_statistics " . $where_clause; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared $summary = $wpdb->get_row($summary_sql); include_once AIPA_PLUGIN_DIR . 'templates/admin/statistics-page.php'; } /** * Product Fields Section callback */ public function product_fields_section_callback() { echo '
' . esc_html(__('Select which product details the AI reads when deciding which products best match a customer\'s search. More fields give the AI more context, but also use more API credits per search.', 'ai-product-assistant')) . '
'; } /** * Search Section callback */ public function search_section_callback() { echo '' . esc_html(__('Configure AI product search settings.', 'ai-product-assistant')) . '
'; } /** * Search UI Section callback */ public function search_ui_section_callback() { echo '' . esc_html(__('Customize the text and appearance of the AI search interface.', 'ai-product-assistant')) . '
'; echo '' . esc_html(__('These settings apply to the search form displayed in the modal and shortcode.', 'ai-product-assistant')) . '
'; echo '' . esc_html(__('Configure cart analysis settings.', 'ai-product-assistant')) . '
'; } /** * Cart UI Section callback */ public function cart_ui_section_callback() { echo '' . esc_html(__('Customize the text and appearance of the cart analysis interface.', 'ai-product-assistant')) . '
'; } /** * Performance Section callback */ public function performance_section_callback() { echo '' . esc_html(__('Configure AI search performance and efficiency settings.', 'ai-product-assistant')) . '
'; } /** * Statistics Section callback */ public function statistics_section_callback() { echo '' . esc_html(__('View API usage statistics and export data.', 'ai-product-assistant')) . '
'; } /** * Product Assistant Section callback */ public function product_assistant_section_callback() { echo '' . esc_html(__('Configure AI product assistant features for individual products.', 'ai-product-assistant')) . '
'; } /** * Search Fields callback */ public function search_fields_callback() { $options = get_option('aipa_settings'); $search_fields = isset($options['search_fields']) ? $options['search_fields'] : array(); $custom_fields = isset($options['custom_fields']) ? $options['custom_fields'] : array(); $fields = array( 'title' => __('Product Title', 'ai-product-assistant'), 'short_description' => __('Short Description', 'ai-product-assistant'), 'content' => __('Long Description/Content', 'ai-product-assistant'), 'categories' => __('Categories', 'ai-product-assistant'), 'tags' => __('Tags', 'ai-product-assistant'), 'brand' => __('Brand', 'ai-product-assistant'), 'weight' => __('Weight', 'ai-product-assistant'), 'dimensions' => __('Dimensions (Length x Width x Height)', 'ai-product-assistant'), 'variation_data' => __('Variation Data (for Variable Products)', 'ai-product-assistant'), 'attributes' => __('Attributes/Custom Meta Fields', 'ai-product-assistant'), 'regular_price' => __('Regular Price', 'ai-product-assistant'), 'sale_price' => __('Sale Price', 'ai-product-assistant') ); echo '' . esc_html(__('Select which product fields to include when matching with AI:', 'ai-product-assistant')) . '
'; echo '