Plugin Directory

Changeset 3412691


Ignore:
Timestamp:
12/05/2025 10:52:36 PM (4 months ago)
Author:
samuelsilvapt
Message:

1.9.1 new v

Location:
ai-search
Files:
25 added
5 edited

Legend:

Unmodified
Added
Removed
  • ai-search/trunk/admin/class-settings-pages.php

    r3412677 r3412691  
    304304    public function get_custom_fields_for_post_type( $post_type ) {
    305305        global $wpdb;
    306        
     306
     307        $fields = [];
     308
     309        // Get regular custom fields (non-underscore prefixed)
    307310        $query = $wpdb->prepare( "
    308311            SELECT DISTINCT pm.meta_key
    309312            FROM {$wpdb->postmeta} pm
    310313            INNER JOIN {$wpdb->posts} p ON pm.post_id = p.ID
    311             WHERE p.post_type = %s 
    312             AND pm.meta_key NOT LIKE '\_%' 
     314            WHERE p.post_type = %s
     315            AND pm.meta_key NOT LIKE '\_%'
    313316            AND pm.meta_key != 'ai_search_embedding'
    314317            AND pm.meta_value != ''
    315318            ORDER BY pm.meta_key
    316319        ", $post_type );
    317        
    318         $results = $wpdb->get_col( $query );
    319         return $results ? $results : [];
     320
     321        $regular_fields = $wpdb->get_col( $query );
     322        if ( $regular_fields ) {
     323            $fields = array_merge( $fields, $regular_fields );
     324        }
     325
     326        // If ACF is active, get ACF field groups for this post type
     327        if ( function_exists( 'acf_get_field_groups' ) ) {
     328            $field_groups = acf_get_field_groups( [ 'post_type' => $post_type ] );
     329
     330            foreach ( $field_groups as $field_group ) {
     331                $acf_fields = acf_get_fields( $field_group['key'] );
     332
     333                if ( $acf_fields ) {
     334                    foreach ( $acf_fields as $field ) {
     335                        // Only include fields that can be converted to text
     336                        $searchable_types = [
     337                            'text', 'textarea', 'number', 'email', 'url', 'password',
     338                            'wysiwyg', 'select', 'checkbox', 'radio', 'button_group',
     339                            'true_false', 'post_object', 'relationship', 'taxonomy',
     340                            'user', 'date_picker', 'date_time_picker', 'time_picker',
     341                            'color_picker'
     342                        ];
     343
     344                        if ( in_array( $field['type'], $searchable_types ) ) {
     345                            $fields[] = $field['name'];
     346                        }
     347                    }
     348                }
     349            }
     350        }
     351
     352        // Remove duplicates and sort
     353        $fields = array_unique( $fields );
     354        sort( $fields );
     355
     356        return $fields;
    320357    }
    321358
  • ai-search/trunk/admin/views/settings-general.php

    r3412677 r3412691  
    122122    <br/><br/>
    123123
    124     <label for="similarity_threshold">Similarity Threshold (0.5-1):</label><br>
    125     <input type="range" id="similarity_threshold" name="similarity_threshold" min="0.5" max="1" step="0.001" value="<?php echo esc_attr( $similarity_threshold ); ?>" oninput="this.nextElementSibling.value = Number(this.value).toFixed(3); updateThresholdDemo(this.value)">
     124    <label for="similarity_threshold">Similarity Threshold (0.2-1):</label><br>
     125    <input type="range" id="similarity_threshold" name="similarity_threshold" min="0.2" max="1" step="0.001" value="<?php echo esc_attr( $similarity_threshold ); ?>" oninput="this.nextElementSibling.value = Number(this.value).toFixed(3); updateThresholdDemo(this.value)">
    126126    <output><?php echo number_format( $similarity_threshold, 3 ); ?></output>
    127     <p><em>Defines how similar a post must be to appear in search results. Higher values (closer to 1.0) mean stricter, more relevant results. Values below 0.5 often return too many irrelevant matches. Default: 0.500.</em></p>
     127    <p><em>Defines how similar a post must be to appear in search results. Higher values (closer to 1.0) mean stricter, more relevant results. Lower values (0.2-0.4) return more results but may include less relevant matches. Recommended: 0.3-0.5.</em></p>
    128128   
    129129    <!-- Interactive threshold demonstration -->
  • ai-search/trunk/ai-search.php

    r3412677 r3412691  
    33 * Plugin Name: AI Search
    44 * Description: Replaces the default search with an intelligent search system.
    5  * Version: 1.9.0
     5 * Version: 1.9.1
    66 * Author: samuelsilvapt
    77 * License: GPL2
     
    138138
    139139    /**
     140     * Get custom fields content for a post.
     141     *
     142     * @param int $post_id Post ID.
     143     * @param string $post_type Post type.
     144     *
     145     * @return string Custom fields content.
     146     */
     147    private function get_custom_fields_content( $post_id, $post_type ) {
     148        $content = '';
     149
     150        // Get selected custom fields for this post type
     151        $selected_fields = get_option( 'ai_search_custom_fields_' . $post_type, [] );
     152
     153        if ( empty( $selected_fields ) || ! is_array( $selected_fields ) ) {
     154            return $content;
     155        }
     156
     157        // Get custom field values
     158        foreach ( $selected_fields as $field_key ) {
     159            $field_value = get_post_meta( $post_id, $field_key, true );
     160
     161            if ( ! empty( $field_value ) ) {
     162                // Get field label (support ACF if available)
     163                $field_label = $field_key;
     164
     165                // Try to get ACF field label
     166                if ( function_exists( 'get_field_object' ) ) {
     167                    $field_object = get_field_object( $field_key, $post_id );
     168                    if ( $field_object && isset( $field_object['label'] ) ) {
     169                        $field_label = $field_object['label'];
     170                    }
     171                }
     172
     173                // Handle different field value types
     174                if ( is_array( $field_value ) ) {
     175                    // Handle ACF relationship, post object, or other array fields
     176                    $processed_values = [];
     177                    foreach ( $field_value as $item ) {
     178                        if ( is_object( $item ) && isset( $item->post_title ) ) {
     179                            // ACF Post Object or Relationship field
     180                            $processed_values[] = $item->post_title;
     181                        } elseif ( is_string( $item ) ) {
     182                            $processed_values[] = $item;
     183                        } elseif ( is_array( $item ) && isset( $item['label'] ) ) {
     184                            // ACF select/checkbox with labels
     185                            $processed_values[] = $item['label'];
     186                        } elseif ( is_array( $item ) && isset( $item['title'] ) ) {
     187                            // ACF image/file field
     188                            $processed_values[] = $item['title'];
     189                        }
     190                    }
     191                    if ( ! empty( $processed_values ) ) {
     192                        $field_value = implode( ' ', $processed_values );
     193                    } else {
     194                        continue; // Skip if we couldn't extract meaningful data
     195                    }
     196                } elseif ( is_object( $field_value ) ) {
     197                    // Handle ACF User field or other object types
     198                    if ( isset( $field_value->display_name ) ) {
     199                        $field_value = $field_value->display_name;
     200                    } elseif ( isset( $field_value->post_title ) ) {
     201                        $field_value = $field_value->post_title;
     202                    } else {
     203                        continue; // Skip objects we can't process
     204                    }
     205                }
     206
     207                // Sanitize and add to content with field label
     208                if ( is_string( $field_value ) ) {
     209                    $content .= ' ' . sanitize_text_field( $field_label ) . ': ' . sanitize_text_field( $field_value );
     210                }
     211            }
     212        }
     213
     214        // Handle WooCommerce products if applicable
     215        if ( $post_type === 'product' && class_exists( 'WooCommerce' ) ) {
     216            $woo_fields = get_option( 'ai_search_woocommerce_fields', [] );
     217
     218            if ( ! empty( $woo_fields ) && is_array( $woo_fields ) ) {
     219                $product = wc_get_product( $post_id );
     220
     221                if ( $product ) {
     222                    foreach ( $woo_fields as $woo_field ) {
     223                        switch ( $woo_field ) {
     224                            case 'product_description':
     225                                $desc = $product->get_description();
     226                                if ( ! empty( $desc ) ) {
     227                                    $content .= ' product_description: ' . sanitize_textarea_field( $desc );
     228                                }
     229                                break;
     230                            case 'product_short_description':
     231                                $short_desc = $product->get_short_description();
     232                                if ( ! empty( $short_desc ) ) {
     233                                    $content .= ' product_short_description: ' . sanitize_textarea_field( $short_desc );
     234                                }
     235                                break;
     236                            case 'product_sku':
     237                                $sku = $product->get_sku();
     238                                if ( ! empty( $sku ) ) {
     239                                    $content .= ' product_sku: ' . sanitize_text_field( $sku );
     240                                }
     241                                break;
     242                            case 'product_categories':
     243                                $categories = wp_get_post_terms( $post_id, 'product_cat', [ 'fields' => 'names' ] );
     244                                if ( ! is_wp_error( $categories ) && ! empty( $categories ) ) {
     245                                    $content .= ' product_categories: ' . implode( ' ', array_map( 'sanitize_text_field', $categories ) );
     246                                }
     247                                break;
     248                            case 'product_tags':
     249                                $tags = wp_get_post_terms( $post_id, 'product_tag', [ 'fields' => 'names' ] );
     250                                if ( ! is_wp_error( $tags ) && ! empty( $tags ) ) {
     251                                    $content .= ' product_tags: ' . implode( ' ', array_map( 'sanitize_text_field', $tags ) );
     252                                }
     253                                break;
     254                        }
     255                    }
     256
     257                    // Handle product attributes
     258                    $attributes = $product->get_attributes();
     259                    foreach ( $attributes as $attribute ) {
     260                        $attribute_key = 'attribute_' . $attribute->get_name();
     261                        if ( in_array( $attribute_key, $woo_fields ) ) {
     262                            $values = $attribute->get_options();
     263                            if ( ! empty( $values ) ) {
     264                                $attribute_label = wc_attribute_label( $attribute->get_name() );
     265                                $attribute_values = [];
     266
     267                                if ( is_array( $values ) ) {
     268                                    foreach ( $values as $value ) {
     269                                        $term = get_term( $value );
     270                                        if ( $term && ! is_wp_error( $term ) ) {
     271                                            $attribute_values[] = sanitize_text_field( $term->name );
     272                                        }
     273                                    }
     274                                } else {
     275                                    $attribute_values[] = sanitize_text_field( $values );
     276                                }
     277
     278                                if ( ! empty( $attribute_values ) ) {
     279                                    $content .= ' ' . $attribute_label . ': ' . implode( ' ', $attribute_values );
     280                                }
     281                            }
     282                        }
     283                    }
     284                }
     285            }
     286        }
     287
     288        return trim( $content );
     289    }
     290
     291    /**
    140292     * Get embedding from OpenAI.
    141293     *
     
    146298    private function get_embedding( $content ) {
    147299        $content = sanitize_textarea_field( $content );
     300
    148301        // Check for Cached Embedding
    149302        $cached_embedding = get_transient( 'ai_search_embedding_' . md5( $content ) );
     
    158311                return false; // No token available, service not registered
    159312            }
    160            
     313
    161314            $embedding = $this->service_client->get_embedding( $content );
    162315            if ( $embedding && ! empty( $embedding['embedding']) ) {
     
    206359
    207360        $post_type = $query->get( 'post_type' );
    208    
     361
    209362        $search_query = get_search_query();
    210363        $search_query = sanitize_text_field( $search_query );
    211364        $query_embedding = $this->get_embedding( $search_query );
    212    
     365
    213366        if ( ! $query_embedding ) {
    214367            return $posts;
    215368        }
    216    
     369
    217370        $similarities = [];
    218371
     
    224377        ]);
    225378
    226        
     379
    227380        foreach ( $all_posts as $post ) {
    228381
     
    246399            return $this->enhanced_fallback_search( $search_query, $post_type, $posts );
    247400        }
    248    
     401
    249402        arsort( $similarities );
    250403        $ordered_ids = array_keys( $similarities );
     
    257410            'posts_per_page' => -1,
    258411        ]);
    259        
     412
    260413        return $posts;
    261414    }
  • ai-search/trunk/includes/class-ai-search-service.php

    r3385425 r3412691  
    7171        }
    7272
    73         $body = json_decode( wp_remote_retrieve_body( $response ), true );
     73        $status_code = wp_remote_retrieve_response_code( $response );
     74        $response_body = wp_remote_retrieve_body( $response );
     75        $body = json_decode( $response_body, true );
    7476
    75         if ( isset( $body['embeddings'][0]['embedding'], $body['embeddings'][0]['id'] ) ) {
    76             $result = json_decode( $body['embeddings'][0]['embedding'] );
    77             set_transient( $cache_key, $result, MONTH_IN_SECONDS );
    78             return $result;
     77        if ( $status_code !== 200 ) {
     78            return false;
     79        }
     80
     81        if ( isset( $body['embeddings'][0]['embedding'] ) ) {
     82            $embedding = $body['embeddings'][0]['embedding'];
     83
     84            // Check if embedding is a string that needs decoding or already an array
     85            if ( is_string( $embedding ) ) {
     86                $result = json_decode( $embedding, true );
     87            } else {
     88                $result = $embedding;
     89            }
     90
     91            if ( ! is_array( $result ) || empty( $result ) ) {
     92                return false;
     93            }
     94
     95            // Prepare the return value with both embedding and id/index
     96            $return_value = [
     97                'embedding' => $result,
     98                'id' => isset( $body['embeddings'][0]['id'] ) ? $body['embeddings'][0]['id'] : ( isset( $body['embeddings'][0]['index'] ) ? $body['embeddings'][0]['index'] : 0 )
     99            ];
     100
     101            set_transient( $cache_key, $return_value, MONTH_IN_SECONDS );
     102            return $return_value;
    79103        }
    80104
     
    106130
    107131        if ( is_wp_error( $response ) ) {
    108             // Log the error for debugging
    109             error_log( 'AI Search Token Validation Error: ' . $response->get_error_message() );
    110132            return false;
    111133        }
     
    113135        $status_code = wp_remote_retrieve_response_code( $response );
    114136        $body = json_decode( wp_remote_retrieve_body( $response ), true );
    115        
    116         // Log the response for debugging
    117         error_log( 'AI Search Token Validation Response: ' . wp_remote_retrieve_body( $response ) );
    118        
     137
    119138        return $body;
    120139    }
  • ai-search/trunk/readme.txt

    r3412677 r3412691  
    33Tags: search, AI, OpenAI, WordPress
    44Tested up to: 6.8
    5 Stable tag: 1.9.0
     5Stable tag: 1.9.1
    66Requires PHP: 8.0
    77License: GPLv2
Note: See TracChangeset for help on using the changeset viewer.