container = $container; } /** * Builds the SQL query for the search engine. * * @param string $keyword * @param array $settings * @return string */ public function build_query($keyword, $settings = [], $category = '') { global $wpdb; $table_name = $wpdb->prefix . 'awas_search_index'; $keyword = sanitize_text_field($keyword); // 1. Stop Words Filtering $keyword = \Awas\AdvancedSearch\Core\Helper::sanitize_and_tokenize($keyword, $settings); // Settings defaults $max_results = isset($settings['logic']['max_results']) ? absint($settings['logic']['max_results']) : 10; $fuzzy = isset($settings['logic']['fuzzy_match']) ? (bool) $settings['logic']['fuzzy_match'] : true; $where = []; $where[] = $wpdb->prepare("status = %s", 'publish'); // 2. Multilingual Support (WPML / Polylang) if (!empty($settings['advanced']['multilingual'])) { $lang = apply_filters('wpml_current_language', null); if (!$lang && function_exists('pll_current_language')) { $lang = pll_current_language('slug'); } // Some products might have empty lang if created before WPML, so we allow empty or matched lang if ($lang) { $where[] = $wpdb->prepare("(lang = %s OR lang = '')", $lang); } } if (!empty($category)) { // Since our search_index table doesn't have a specific category column right now, // and we stored categories in the 'search_terms', we can enforce that the category // slug or name exists in the search_terms for a simple implementation. // Ideally, we'd join wp_term_relationships, but for performance, we can just do: $cat_term = get_term_by('slug', sanitize_text_field($category), 'product_cat'); if ($cat_term) { $where[] = $wpdb->prepare("search_terms LIKE %s", '%' . $wpdb->esc_like($cat_term->name) . '%'); } } // 3. Synonyms $search_keywords = [$keyword]; if (!empty($settings['advanced']['synonyms'])) { $synonyms_raw = explode("\n", $settings['advanced']['synonyms']); foreach ($synonyms_raw as $rule) { $rule = explode('=', $rule); if (count($rule) === 2) { $word1 = trim(strtolower($rule[0])); $word2 = trim(strtolower($rule[1])); $word1_pattern = '/\b' . preg_quote($word1, '/') . '\b/'; $word2_pattern = '/\b' . preg_quote($word2, '/') . '\b/'; if (preg_match($word1_pattern, $keyword)) { $search_keywords[] = preg_replace($word1_pattern, $word2, $keyword); } elseif (preg_match($word2_pattern, $keyword)) { $search_keywords[] = preg_replace($word2_pattern, $word1, $keyword); } } } } $search_keywords = array_unique(array_filter($search_keywords)); if (empty($search_keywords)) { return ""; } $all_search_conditions = []; foreach ($search_keywords as $kw) { $search_conditions = []; $words = array_filter(explode(' ', $kw)); // Exact SKU Match (Highest priority) $search_conditions[] = $wpdb->prepare("sku = %s", $kw); // Exact Title Match $search_conditions[] = $wpdb->prepare("title LIKE %s", '%' . $wpdb->esc_like($kw) . '%'); // Fuzzy matching logic on search_terms (FULLTEXT OR LIKE) if ($fuzzy && !empty($words)) { $like_conditions = []; foreach ($words as $word) { $like_conditions[] = $wpdb->prepare("search_terms LIKE %s", '%' . $wpdb->esc_like($word) . '%'); } $search_conditions[] = "(" . implode(" AND ", $like_conditions) . ")"; } elseif (!empty($words)) { $search_conditions[] = $wpdb->prepare("search_terms LIKE %s", '%' . $wpdb->esc_like($kw) . '%'); } $all_search_conditions[] = "(" . implode(" OR ", $search_conditions) . ")"; } $where[] = "(" . implode(" OR ", $all_search_conditions) . ")"; $where_sql = implode(" AND ", $where); // Calculate relevance: Exact SKU (100), Exact Title (50), Partial (10) $select = " object_id, variation_id, title, sku, ( IF(sku = '" . esc_sql($keyword) . "', 100, 0) + IF(title LIKE '%" . esc_sql($keyword) . "%', 50, 0) ) as relevance "; $sql = "SELECT $select FROM $table_name WHERE $where_sql ORDER BY relevance DESC, title ASC LIMIT $max_results"; return $sql; } }