Plugin Directory

Changeset 3330738


Ignore:
Timestamp:
07/19/2025 07:10:13 PM (9 months ago)
Author:
muchatai
Message:

Update to version 2.0.38 from GitHub

Location:
muchat-ai
Files:
24 edited
1 copied

Legend:

Unmodified
Added
Removed
  • muchat-ai/tags/2.0.38/assets/js/admin.js

    r3300525 r3330738  
    144144
    145145    $(document).ready(function() {
    146         // Initial Messages Management
    147         var container = document.getElementById('muchat-initial-messages-list');
    148         var addBtn = document.getElementById('muchat-add-initial-message');
    149         var hiddenInput = document.getElementById('muchat_ai_chatbot_interface_initial_messages');
    150        
    151         if (container && addBtn && hiddenInput) {
    152             var initial = [];
     146        // --- Reusable function for Initial Messages Management ---
     147        function initializeMessageList(containerId, buttonId, inputId) {
     148            var container = document.getElementById(containerId);
     149            var addBtn = document.getElementById(buttonId);
     150            var hiddenInput = document.getElementById(inputId);
     151
     152            if (!container || !addBtn || !hiddenInput) {
     153                return; // Exit if elements are not found
     154            }
     155
     156            var messages = [];
    153157
    154158            // Parse initial messages with error handling
     
    156160                if (hiddenInput.value && hiddenInput.value.trim() !== '') {
    157161                    try {
    158                         initial = JSON.parse(hiddenInput.value);
    159                         if (!Array.isArray(initial)) {
     162                        messages = JSON.parse(hiddenInput.value);
     163                        if (!Array.isArray(messages)) {
    160164                            console.warn('Initial messages not in expected array format, converting...');
    161                             initial = hiddenInput.value.split(',').map(function(x) {
     165                            messages = hiddenInput.value.split(',').map(function(x) {
    162166                                return x.trim();
    163167                            });
    164168                        }
    165169                    } catch (e) {
    166                         console.warn('Failed to parse JSON, falling back to comma separation', e);
    167                         initial = hiddenInput.value.split(',').map(function(x) {
     170                        console.warn('Failed to parse JSON, falling back to comma separation for ' + inputId, e);
     171                        messages = hiddenInput.value.split(',').map(function(x) {
    168172                            return x.trim();
    169173                        });
     
    171175                }
    172176            } catch (e) {
    173                 console.error('Error processing initial messages:', e);
    174                 initial = [];
     177                console.error('Error processing initial messages for ' + inputId + ':', e);
     178                messages = [];
    175179            }
    176180
    177181            function render() {
    178182                container.innerHTML = '';
    179                 initial.forEach(function(msg, idx) {
     183                messages.forEach(function(msg, idx) {
    180184                    var div = document.createElement('div');
    181185                    div.className = 'initial-message-row';
     
    183187                    input.type = 'text';
    184188                    input.className = 'regular-text';
    185                     input.value = msg || ''; // Prevent undefined values
     189                    input.value = msg || '';
    186190                    input.placeholder = 'Message...';
    187191                    input.oninput = function() {
    188                         initial[idx] = input.value;
     192                        messages[idx] = input.value;
    189193                        updateHidden();
    190194                    };
     
    195199                    remove.title = 'Remove message';
    196200                    remove.onclick = function() {
    197                         initial.splice(idx, 1);
     201                        messages.splice(idx, 1);
    198202                        render();
    199203                        updateHidden();
     
    208212            function updateHidden() {
    209213                try {
    210                     var filtered = initial.map(function(x) {
     214                    var filtered = messages.map(function(x) {
    211215                        return (x || '').trim();
    212216                    }).filter(Boolean);
    213217                    hiddenInput.value = JSON.stringify(filtered);
    214218                } catch (e) {
    215                     console.error('Error updating hidden input:', e);
     219                    console.error('Error updating hidden input for ' + inputId + ':', e);
    216220                }
    217221            }
    218222
    219223            addBtn.onclick = function() {
    220                 initial.push('');
     224                messages.push('');
    221225                render();
    222226            };
     
    224228            render();
    225229        }
     230       
     231        // Initialize both message lists
     232        initializeMessageList('muchat-initial-messages-list', 'muchat-add-initial-message', 'muchat_ai_chatbot_interface_initial_messages');
     233        initializeMessageList('muchat-guest-initial-messages-list', 'muchat-add-guest-initial-message', 'muchat_ai_chatbot_guest_initial_messages');
     234
     235        // --- Toggle for Guest Initial Messages ---
     236        var guestToggleCheckbox = document.getElementById('muchat_ai_chatbot_enable_guest_messages');
     237        var guestMessagesRow = document.getElementById('muchat-guest-initial-messages-row');
     238
     239        if (guestToggleCheckbox && guestMessagesRow) {
     240            function toggleGuestMessages() {
     241                guestMessagesRow.style.display = guestToggleCheckbox.checked ? 'table-row' : 'none';
     242            }
     243           
     244            // Set initial state on page load
     245            toggleGuestMessages();
     246
     247            // Add event listener for changes
     248            guestToggleCheckbox.addEventListener('change', toggleGuestMessages);
     249        }
     250
    226251
    227252        // Schedule Settings Toggle
  • muchat-ai/tags/2.0.38/includes/Admin/Settings.php

    r3309949 r3330738  
    248248        $interface_primary_color = get_option('muchat_ai_chatbot_interface_primary_color', '#145dee');
    249249        $interface_initial_messages = get_option('muchat_ai_chatbot_interface_initial_messages', '');
     250        $enable_guest_messages = get_option('muchat_ai_chatbot_enable_guest_messages', '0');
     251        $guest_initial_messages = get_option('muchat_ai_chatbot_guest_initial_messages', '');
    250252        $load_strategy = get_option('muchat_ai_chatbot_load_strategy', 'FAST');
    251253        $use_logged_in_user_info = get_option('muchat_ai_chatbot_use_logged_in_user_info', '');
     
    544546    public function register_settings()
    545547    {
     548        // Handle cache clearing action
     549        if (
     550            isset($_GET['action']) &&
     551            $_GET['action'] === 'muchat_refresh_meta' &&
     552            isset($_GET['_wpnonce']) &&
     553            wp_verify_nonce(sanitize_key($_GET['_wpnonce']), 'muchat_refresh_meta_action')
     554        ) {
     555            delete_transient('muchat_product_meta_fields_cache');
     556            add_settings_error(
     557                'muchat_ai_chatbot_plugin_messages',
     558                'meta_cache_cleared',
     559                __('Product meta fields cache has been cleared successfully.', 'muchat-ai'),
     560                'updated'
     561            );
     562        }
     563
    546564        // Register plugin settings
    547565        $this->register_plugin_settings();
     
    550568        $this->register_plugin_onboarding();
    551569
    552         register_setting('muchat_ai_chatbot_plugin_settings', 'muchat_ai_chatbot_plugin_options', array(
    553             'sanitize_callback' => function ($input) {
    554                 // If input is not an array, return an empty array
    555                 if (!is_array($input)) {
    556                     return array();
     570        // Register the main settings group for the plugin
     571        register_setting(
     572            'muchat_ai_chatbot_plugin_settings',
     573            'muchat_ai_chatbot_plugin_options',
     574            array(
     575                'sanitize_callback' => function ($input) {
     576                    // If input is not an array, return an empty array
     577                    if (!is_array($input)) {
     578                        return array();
     579                    }
     580
     581                    // Sanitize meta field selections (array of values)
     582                    if (isset($input['product_meta_fields']) && is_array($input['product_meta_fields'])) {
     583                        $input['product_meta_fields'] = array_map('sanitize_text_field', $input['product_meta_fields']);
     584                    }
     585
     586                    // Sanitize meta labels (associative array)
     587                    if (isset($input['meta_labels']) && is_array($input['meta_labels'])) {
     588                        foreach ($input['meta_labels'] as $key => $value) {
     589                            $input['meta_labels'][$key] = sanitize_text_field($value);
     590                        }
     591                    }
     592
     593                    return $input;
    557594                }
    558 
    559                 // Sanitize meta field selections (array of values)
    560                 if (isset($input['product_meta_fields']) && is_array($input['product_meta_fields'])) {
    561                     $input['product_meta_fields'] = array_map('sanitize_text_field', $input['product_meta_fields']);
    562                 }
    563 
    564                 // Sanitize meta labels (associative array)
    565                 if (isset($input['meta_labels']) && is_array($input['meta_labels'])) {
    566                     foreach ($input['meta_labels'] as $key => $value) {
    567                         $input['meta_labels'][$key] = sanitize_text_field($value);
    568                     }
    569                 }
    570 
    571                 return $input;
    572             }
    573         ));
     595            )
     596        );
    574597
    575598        // Product Meta Section (in a separate page section)
     
    734757            'default' => ''
    735758        ));
     759        register_setting('muchat-settings-group', 'muchat_ai_chatbot_enable_guest_messages', array(
     760            'type' => 'string',
     761            'sanitize_callback' => 'sanitize_text_field',
     762            'default' => '0'
     763        ));
     764        register_setting('muchat-settings-group', 'muchat_ai_chatbot_guest_initial_messages', array(
     765            'type' => 'string',
     766            'sanitize_callback' => 'sanitize_textarea_field',
     767            'default' => ''
     768        ));
    736769        register_setting('muchat-settings-group', 'muchat_ai_chatbot_load_strategy', array(
    737770            'type' => 'string',
     
    806839        add_option('muchat_ai_chatbot_onboarding', false);
    807840
     841        // Add default options for the new guest message fields
     842        add_option('muchat_ai_chatbot_enable_guest_messages', '0');
     843        add_option('muchat_ai_chatbot_guest_initial_messages', '');
     844
    808845        // Default load strategy
    809846        add_option('muchat_ai_chatbot_load_strategy', 'FAST');
  • muchat-ai/tags/2.0.38/includes/Api/Middleware/AuthMiddleware.php

    r3300525 r3330738  
    5050    private function validate_token($token)
    5151    {
     52        $transient_key = 'muchat_token_' . md5($token);
     53
     54        // Check cache first
     55        if (get_transient($transient_key)) {
     56            return true;
     57        }
     58
    5259        global $wp_filter;
    5360
     
    105112            }
    106113
     114            // If validation is successful, store it in cache for 1 hour
     115            set_transient($transient_key, true, HOUR_IN_SECONDS);
     116
    107117            return true;
    108118        } finally {
  • muchat-ai/tags/2.0.38/includes/Core/Plugin.php

    r3300592 r3330738  
    1818     */
    1919    protected $version;
     20
     21    /**
     22     * Settings class instance
     23     *
     24     * @var \Muchat\Api\Admin\Settings
     25     */
     26    private $settings_instance;
    2027
    2128    /**
     
    5663
    5764    /**
     65     * Get (and instantiate if necessary) the settings admin class
     66     *
     67     * @return \Muchat\Api\Admin\Settings
     68     */
     69    public function get_settings_instance()
     70    {
     71        if (null === $this->settings_instance) {
     72            $this->settings_instance = new \Muchat\Api\Admin\Settings();
     73        }
     74        return $this->settings_instance;
     75    }
     76
     77    /**
    5878     * Register admin hooks
    5979     *
     
    6282    private function setup_admin()
    6383    {
    64         $admin = new \Muchat\Api\Admin\Settings();
    65 
    66         $this->loader->add_action('admin_menu', $admin, 'add_menu_page');
    67         $this->loader->add_action('admin_init', $admin, 'register_settings');
    68         $this->loader->add_action('admin_enqueue_scripts', $admin, 'enqueue_styles');
    69         $this->loader->add_action('admin_enqueue_scripts', $admin, 'enqueue_scripts');
     84        $this->loader->add_action('admin_menu', $this, 'add_admin_menu');
     85        $this->loader->add_action('admin_init', $this, 'register_admin_settings');
     86        $this->loader->add_action('admin_enqueue_scripts', $this, 'enqueue_admin_assets');
     87
     88    }
     89
     90   
     91    /**
     92     * Wrapper for adding the admin menu
     93     */
     94    public function add_admin_menu()
     95    {
     96        $this->get_settings_instance()->add_menu_page();
     97    }
     98
     99    /**
     100     * Wrapper for registering settings
     101     */
     102    public function register_admin_settings()
     103    {
     104        $this->get_settings_instance()->register_settings();
     105    }
     106
     107    /**
     108     * Wrapper for enqueuing admin assets
     109     *
     110     * @param string $hook The current admin page hook.
     111     */
     112    public function enqueue_admin_assets($hook)
     113    {
     114        $this->get_settings_instance()->enqueue_styles($hook);
     115        $this->get_settings_instance()->enqueue_scripts($hook);
    70116    }
    71117
  • muchat-ai/tags/2.0.38/includes/Frontend/Widget.php

    r3300525 r3330738  
    8080    /**
    8181     * Determines if the chatbot should be displayed on the current page
    82      * based on visibility settings
     82     * based on visibility settings. This is the primary logic gate.
    8383     */
    8484    private function should_display()
    8585    {
    86         // Check if widget is enabled
    87         $widget_enabled = get_option('muchat_ai_chatbot_widget_enabled', '1');
    88         if ($widget_enabled !== '1') {
     86        // 1. Check if widget is globally enabled
     87        if (get_option('muchat_ai_chatbot_widget_enabled', '1') !== '1') {
    8988            return false;
    9089        }
    9190
    92         // Check scheduling if enabled
    93         $schedule_enabled = get_option('muchat_ai_chatbot_schedule_enabled', '0');
    94         if ($schedule_enabled === '1') {
     91        // 2. Check scheduling if enabled
     92        if (get_option('muchat_ai_chatbot_schedule_enabled', '0') === '1') {
    9593            if (!$this->is_within_schedule()) {
    9694                return false;
     
    9896        }
    9997
    100         // Get visibility settings
     98        // 3. Check page visibility rules
    10199        $visibility_mode = get_option('muchat_ai_chatbot_visibility_mode', 'all');
    102100        $visibility_pages = get_option('muchat_ai_chatbot_visibility_pages', '');
    103101
    104         // If visibility pages is empty...
    105         if (empty($visibility_pages)) {
    106             // For "show everywhere except" mode, show on all pages
    107             if ($visibility_mode === 'all') {
    108                 return true;
    109             }
    110             // For "only show on listed pages" mode, don't show anywhere if list is empty
    111             else {
    112                 return false;
    113             }
    114         }
    115 
    116         // If wildcard is specified, show everywhere for 'all' mode
    117         if (trim($visibility_pages) === '*') {
     102        // Rule: 'Disabled Everywhere'
     103        if ($visibility_mode === 'none') {
     104            return false;
     105        }
     106
     107        // Rule: 'Show on all pages' (and no exceptions are listed)
     108        if ($visibility_mode === 'all' && empty(trim($visibility_pages))) {
    118109            return true;
    119110        }
    120111
    121         // Get current path
    122         $current_path = isset($_SERVER['REQUEST_URI']) ? esc_url_raw(wp_unslash($_SERVER['REQUEST_URI'])) : '';
    123 
    124         // Check if this is the homepage
    125         $is_front = is_front_page() || $current_path === '/' || $current_path === '/index.php';
    126 
    127         // Convert path patterns to array
    128         $patterns = array_filter(explode("\n", $visibility_pages));
    129         $matches = false;
     112        // Rule: 'Only show on listed pages' (and the list is empty)
     113        if ($visibility_mode === 'specific' && empty(trim($visibility_pages))) {
     114            return false;
     115        }
     116
     117        // The logic now depends on matching the current page against the list.
     118        $matches = $this->is_path_match($visibility_pages);
     119
     120        // Rule: 'Show on all pages EXCEPT those listed'
     121        if ($visibility_mode === 'all') {
     122            return !$matches;
     123        }
     124
     125        // Rule: 'Only show on pages listed'
     126        if ($visibility_mode === 'specific') {
     127            return $matches;
     128        }
     129
     130        // Default fallback, should ideally not be reached.
     131        return false;
     132    }
     133
     134    /**
     135     * Checks if the current request path matches any of the given patterns.
     136     * Handles exact, wildcard, UTF-8, percent-encoded, and <front> patterns.
     137     *
     138     * @param string $patterns_string A newline-separated string of URL patterns.
     139     * @return bool True if the path matches one of the patterns, false otherwise.
     140     */
     141    private function is_path_match($patterns_string)
     142    {
     143        // 1. Get and normalize the current page's path.
     144        // We get the raw URI, strip any query parameters, and then decode it.
     145        $request_uri = isset($_SERVER['REQUEST_URI']) ? wp_unslash($_SERVER['REQUEST_URI']) : '';
     146        $path_only = strtok($request_uri, '?');
     147        $current_path = rtrim(urldecode($path_only), '/');
     148
     149        // Treat an empty path (which can be the homepage) as '/'.
     150        if (empty($current_path)) {
     151            $current_path = '/';
     152        }
     153
     154        // 2. Use WordPress's reliable function to determine if it's the front page.
     155        $is_front = is_front_page();
     156
     157        // 3. Handle the global wildcard match immediately.
     158        if (trim($patterns_string) === '*') {
     159            return true;
     160        }
     161
     162        // 4. Process the list of patterns.
     163        $patterns = explode("\n", $patterns_string);
    130164
    131165        foreach ($patterns as $pattern) {
    132166            $pattern = trim($pattern);
    133 
    134             // Skip empty lines
    135167            if (empty($pattern)) {
    136168                continue;
    137169            }
    138170
    139             // Check for front page
    140             if ($pattern === '<front>' && $is_front) {
    141                 $matches = true;
    142                 break;
    143             }
    144 
    145             // Check for exact matches
    146             if ($pattern === $current_path) {
    147                 $matches = true;
    148                 break;
    149             }
    150 
    151             // Check for wildcard matches
    152             if (strpos($pattern, '*') !== false) {
    153                 $regex_pattern = '@^' . str_replace('*', '.*', $pattern) . '$@';
    154                 if (preg_match($regex_pattern, $current_path)) {
    155                     $matches = true;
    156                     break;
     171            // Also decode the user-provided pattern in case it's percent-encoded.
     172            $decoded_pattern = urldecode($pattern);
     173
     174            // A. Check for the special '<front>' tag.
     175            if ($decoded_pattern === '<front>') {
     176                if ($is_front) {
     177                    return true; // Match found.
    157178                }
    158             }
    159         }
    160 
    161         // Return based on visibility mode
    162         if ($visibility_mode === 'all') {
    163             // Show on all pages EXCEPT those listed
    164             return !$matches;
    165         } else {
    166             // Only show on pages listed
    167             return $matches;
    168         }
     179                continue; // Not the front page, so check next pattern.
     180            }
     181
     182            // B. Normalize the pattern for comparison.
     183            $normalized_pattern = rtrim($decoded_pattern, '/');
     184            if (empty($normalized_pattern)) {
     185                $normalized_pattern = '/';
     186            }
     187
     188            // C. Check for wildcard matches.
     189            if (strpos($normalized_pattern, '*') !== false) {
     190                // Escape regex characters, then replace our wildcard `*` with `.*`.
     191                // The 'u' modifier is crucial for correct UTF-8 pattern matching.
     192                $regex = '@^' . str_replace('\*', '.*', preg_quote($normalized_pattern, '@')) . '$@u';
     193                if (preg_match($regex, $current_path)) {
     194                    return true; // Match found.
     195                }
     196            }
     197            // D. Check for exact matches (only if no wildcard).
     198            else {
     199                if ($normalized_pattern === $current_path) {
     200                    return true; // Match found.
     201                }
     202                // Also check if the pattern is for the homepage and it is the homepage
     203                if ($normalized_pattern === '/' && $is_front) {
     204                    return true;
     205                }
     206            }
     207        }
     208
     209        // No patterns matched the current path.
     210        return false;
    169211    }
    170212
     
    341383
    342384    /**
    343      * Process and get initial messages
     385     * Get initial messages based on user login status
     386     *
     387     * @return array
    344388     */
    345389    private function get_initial_messages()
    346390    {
    347         $interface_initial_messages = get_option('muchat_ai_chatbot_interface_initial_messages', '');
    348         if (empty($interface_initial_messages)) {
    349             return [];
    350         }
    351 
    352         // Parse initial messages
    353         $initial_messages = [];
     391        // Get guest-specific message settings
     392        $enable_guest_messages = get_option('muchat_ai_chatbot_enable_guest_messages', '0');
     393        $guest_initial_messages_json = get_option('muchat_ai_chatbot_guest_initial_messages', '');
     394
     395        // Check if we should use guest messages
     396        if ('1' === $enable_guest_messages && !is_user_logged_in() && !empty($guest_initial_messages_json)) {
     397            try {
     398                $guest_messages = json_decode($guest_initial_messages_json, true);
     399                if (is_array($guest_messages) && !empty($guest_messages)) {
     400                    return $guest_messages;
     401                }
     402            } catch (\Exception $e) {
     403                // If JSON is invalid, fall through to default messages
     404            }
     405        }
     406
     407        // --- Fallback to default messages ---
     408        $initial_messages_json = get_option('muchat_ai_chatbot_interface_initial_messages', '');
     409        if (empty($initial_messages_json)) {
     410            return []; // No messages configured
     411        }
     412
    354413        try {
    355             $decoded = json_decode($interface_initial_messages, true);
    356             if (is_array($decoded)) {
    357                 $initial_messages = $decoded;
    358             } else {
    359                 // Fallback to comma-separated
    360                 $initial_messages = array_map('trim', explode(',', $interface_initial_messages));
     414            $initial_messages = json_decode($initial_messages_json, true);
     415            // Ensure it's a non-empty array
     416            if (is_array($initial_messages) && !empty($initial_messages)) {
     417                return $initial_messages;
    361418            }
    362419        } catch (\Exception $e) {
    363             // Fallback to empty array if parsing fails
    364             return [];
    365         }
    366 
    367         // Replace variables
    368         $firstName = '';
    369         $lastName = '';
    370         if (is_user_logged_in()) {
    371             $current_user = wp_get_current_user();
    372             $firstName = $current_user->user_firstname;
    373             $lastName = $current_user->user_lastname;
    374         } else {
    375             $firstName = get_option('muchat_ai_chatbot_contact_first_name', '');
    376             $lastName = get_option('muchat_ai_chatbot_contact_last_name', '');
    377         }
    378 
    379         foreach ($initial_messages as &$msg) {
    380             $msg = str_replace(['$name', '$lastname'], [$firstName, $lastName], $msg);
    381         }
    382 
    383         return array_filter($initial_messages);
     420            // JSON might be malformed, or it might be a simple comma-separated string
     421        }
     422
     423        // Fallback for old comma-separated format
     424        $messages = array_map('trim', explode(',', $initial_messages_json));
     425        return array_filter($messages);
    384426    }
    385427
  • muchat-ai/tags/2.0.38/includes/Models/Page.php

    r3300525 r3330738  
    6363        }
    6464
    65         // Get all valid page IDs first (for accurate counting and filtering)
    66         $all_pages_query = new \WP_Query(array_merge(
    67             $args,
    68             ['posts_per_page' => -1]
    69         ));
    70 
    71         // Filter to only get valid pages
    72         $valid_page_ids = [];
    73         foreach ($all_pages_query->posts as $page_id) {
    74             $page = get_post($page_id);
    75             if ($this->is_valid_page($page)) {
    76                 $valid_page_ids[] = $page_id;
    77             }
    78         }
    79 
    80         // Get total count from valid pages
    81         $total_count = count($valid_page_ids);
    82 
    83         // Get requested pages (with pagination)
     65        // Get pages to exclude
     66        $pages_to_exclude = $this->get_pages_to_exclude();
     67        if (!empty($pages_to_exclude)) {
     68            $args['post__not_in'] = $pages_to_exclude;
     69        }
     70
     71        // Add a WHERE clause to filter out empty content
     72        add_filter('posts_where', function ($where) {
     73            global $wpdb;
     74            $where .= " AND {$wpdb->posts}.post_content != ''";
     75            return $where;
     76        }, 10, 1);
     77
     78
     79        // Set pagination parameters
    8480        $requested_offset = $params['skip'] ?? 0;
    8581        $requested_limit = $params['take'] ?? 10;
    8682
    87         // Get the slice of page IDs for this request
    88         $paginated_ids = array_slice($valid_page_ids, $requested_offset, $requested_limit);
     83        $args['posts_per_page'] = $requested_limit;
     84        $args['offset'] = $requested_offset;
     85
     86        // Execute the final query with pagination
     87        $query = new \WP_Query($args);
     88
     89        // Get total count from the same query
     90        $total_count = (int) $query->found_posts;
    8991
    9092        // Format the pages
    91         $pages = [];
    92         foreach ($paginated_ids as $page_id) {
    93             $page = get_post($page_id);
    94             $pages[] = $this->format_page($page);
    95         }
     93        $pages = array_map(function ($page_id) {
     94            return $this->format_page(get_post($page_id));
     95        }, $query->posts);
     96
     97        // Remove the temporary WHERE filter to avoid affecting other queries
     98        remove_filter('posts_where', '__return_true');
     99
    96100
    97101        $plugin = new \Muchat\Api\Core\Plugin();
     
    108112
    109113    /**
    110      * Check if a page is valid (not a WooCommerce page and has content)
    111      *
    112      * @param \WP_Post $page
    113      * @return bool
    114      */
    115     private function is_valid_page($page)
    116     {
    117         if (empty($page) || empty($page->post_content)) {
    118             return false;
    119         }
    120 
     114     * Get an array of page IDs to exclude
     115     *
     116     * @return array
     117     */
     118    private function get_pages_to_exclude()
     119    {
    121120        $woocommerce_pages = [];
    122 
    123121        // Only check WooCommerce pages if WooCommerce is active
    124122        if (class_exists('WooCommerce')) {
    125             // Main WooCommerce pages
    126             $woocommerce_pages = [
     123            $woocommerce_pages = array_filter([
    127124                wc_get_page_id('cart'),
    128125                wc_get_page_id('checkout'),
     
    130127                wc_get_page_id('shop'),
    131128                wc_get_page_id('terms'),
    132             ];
    133         }
    134 
     129            ]);
     130        }
    135131        // Additional pages to exclude regardless of WooCommerce
    136         $additional_pages = [
    137             // Check by slug (different variations)
     132        $additional_pages = array_filter([
    138133            $this->get_page_id_by_slug('wishlist'),
    139134            $this->get_page_id_by_slug('compare'),
     
    142137            $this->get_page_id_by_slug('my-wishlist'),
    143138            $this->get_page_id_by_slug('my-compare'),
    144 
    145             // Check by title (different variations)
    146139            $this->get_page_id_by_title('Wishlist'),
    147140            $this->get_page_id_by_title('Compare'),
     
    151144            $this->get_page_id_by_title('My Compare'),
    152145            $this->get_page_id_by_title('علاقه‌مندی‌ها'), // For Persian sites
    153             $this->get_page_id_by_title('مقایسه')        // For Persian sites
    154         ];
    155 
    156         $pages_to_exclude = array_merge($woocommerce_pages, $additional_pages);
     146            $this->get_page_id_by_title('مقایسه') // For Persian sites
     147        ]);
     148        return array_unique(array_merge($woocommerce_pages, $additional_pages));
     149    }
     150
     151
     152    /**
     153     * Check if a page is valid (not a WooCommerce page and has content)
     154     *
     155     * @param \WP_Post $page
     156     * @return bool
     157     */
     158    private function is_valid_page($page)
     159    {
     160        if (empty($page) || empty($page->post_content)) {
     161            return false;
     162        }
     163
     164        $pages_to_exclude = $this->get_pages_to_exclude();
    157165
    158166        // If the page is one of the excluded system pages, return false
     
    208216        }
    209217
    210         $woocommerce_pages = [];
    211 
    212         // Only check WooCommerce pages if WooCommerce is active
    213         if (class_exists('WooCommerce')) {
    214             // Main WooCommerce pages
    215             $woocommerce_pages = [
    216                 wc_get_page_id('cart'),
    217                 wc_get_page_id('checkout'),
    218                 wc_get_page_id('myaccount'),
    219                 wc_get_page_id('shop'),
    220                 wc_get_page_id('terms'),
    221             ];
    222         }
    223 
    224         // Additional pages to exclude regardless of WooCommerce
    225         $additional_pages = [
    226             // Check by slug (different variations)
    227             $this->get_page_id_by_slug('wishlist'),
    228             $this->get_page_id_by_slug('compare'),
    229             $this->get_page_id_by_slug('wish-list'),
    230             $this->get_page_id_by_slug('compare-list'),
    231             $this->get_page_id_by_slug('my-wishlist'),
    232             $this->get_page_id_by_slug('my-compare'),
    233 
    234             // Check by title (different variations)
    235             $this->get_page_id_by_title('Wishlist'),
    236             $this->get_page_id_by_title('Compare'),
    237             $this->get_page_id_by_title('Wish List'),
    238             $this->get_page_id_by_title('Compare List'),
    239             $this->get_page_id_by_title('My Wishlist'),
    240             $this->get_page_id_by_title('My Compare'),
    241             $this->get_page_id_by_title('علاقه‌مندی‌ها'), // For Persian sites
    242             $this->get_page_id_by_title('مقایسه')        // For Persian sites
    243         ];
    244 
    245         $pages_to_exclude = array_merge($woocommerce_pages, $additional_pages);
     218        $pages_to_exclude = $this->get_pages_to_exclude();
    246219
    247220        // If the page is one of the excluded system pages, return null
  • muchat-ai/tags/2.0.38/includes/Models/Post.php

    r3300525 r3330738  
    6363        }
    6464
    65         // Get all valid post IDs first (for accurate counting and filtering)
    66         $all_posts_query = new \WP_Query(array_merge(
    67             $args,
    68             ['posts_per_page' => -1]
    69         ));
    70 
    71         // Filter to only get valid posts
    72         $valid_post_ids = [];
    73         foreach ($all_posts_query->posts as $post_id) {
    74             $post = get_post($post_id);
    75             if ($this->is_valid_post($post)) {
    76                 $valid_post_ids[] = $post_id;
    77             }
    78         }
    79 
    80         // Get total count from valid posts
    81         $total_count = count($valid_post_ids);
    82 
    83         // Get requested posts (with pagination)
     65        // Add a meta query to exclude posts with empty content
     66        $args['meta_query'] = [
     67            [
     68                'key' => '_wp_old_slug', // A non-existent key to satisfy the structure, the real filtering is done by the where filter.
     69            ],
     70        ];
     71
     72        // Add a WHERE clause to filter out empty content. This is more efficient than a meta_query on post_content.
     73        add_filter('posts_where', function ($where) {
     74            global $wpdb;
     75            $where .= " AND {$wpdb->posts}.post_content != ''";
     76            return $where;
     77        });
     78
     79        // Set pagination parameters
    8480        $requested_offset = $params['skip'] ?? 0;
    8581        $requested_limit = $params['take'] ?? 10;
    8682
    87         // Get the slice of post IDs for this request
    88         $paginated_ids = array_slice($valid_post_ids, $requested_offset, $requested_limit);
     83        // Update the query with pagination parameters
     84        $args['posts_per_page'] = $requested_limit;
     85        $args['offset'] = $requested_offset;
     86
     87        // Execute the final query with pagination
     88        $query = new \WP_Query($args);
     89
     90        // Get total count from the same query
     91        $total_count = (int) $query->found_posts;
    8992
    9093        // Format the posts
    91         $posts = [];
    92         foreach ($paginated_ids as $post_id) {
    93             $post = get_post($post_id);
    94             $posts[] = $this->format_post($post);
    95         }
     94        $posts = array_map(function ($post_id) {
     95            return $this->format_post(get_post($post_id));
     96        }, $query->posts);
     97
    9698
    9799        $plugin = new \Muchat\Api\Core\Plugin();
  • muchat-ai/tags/2.0.38/includes/Models/Product.php

    r3300525 r3330738  
    8181        }
    8282
    83         // Get all valid product IDs first (for accurate counting and filtering)
    84         $all_products_query = new \WP_Query(array_merge(
    85             $args,
    86             ['posts_per_page' => -1]
    87         ));
    88 
    89         // Get total count from valid products
    90         $total_count = count($all_products_query->posts);
    91 
    92         // Get requested products (with pagination)
     83        // Set pagination parameters
    9384        $requested_offset = $params['skip'] ?? 0;
    9485        $requested_limit = $params['take'] ?? 30;
     
    10091        // Execute the final query with pagination
    10192        $query = new \WP_Query($args);
     93
     94        // Get total count from the same query
     95        $total_count = (int) $query->found_posts;
    10296
    10397        // Format the products
     
    657651    public function get_product_meta_fields()
    658652    {
     653        $cache_key = 'muchat_product_meta_fields_cache';
     654        $cached_fields = get_transient($cache_key);
     655
     656        if (false !== $cached_fields) {
     657            return $cached_fields;
     658        }
     659
    659660        global $wpdb;
    660661
     
    768769        });
    769770
    770         return $meta_fields;
    771     }
    772 
    773     /**
    774      * Get sample values for a meta key
     771        // Combine standard meta fields and ACF fields
     772        $all_fields = array_values($meta_fields);
     773
     774        // Store the result in cache for 6 hours
     775        set_transient($cache_key, $all_fields, 6 * HOUR_IN_SECONDS);
     776
     777        return $all_fields;
     778    }
     779
     780    /**
     781     * Get sample values for a meta key from the database
    775782     *
    776783     * @param string $meta_key
  • muchat-ai/tags/2.0.38/muchat-ai.php

    r3309949 r3330738  
    55 * Plugin URI: https://mu.chat
    66 * Description: Muchat, a powerful tool for customer support using artificial intelligence
    7  * Version: 2.0.37
     7 * Version: 2.0.38
    88 * Author: Muchat
    99 * Text Domain: muchat-ai
     
    2727
    2828// Define plugin constants with unique prefix
    29 define('MUCHAT_AI_CHATBOT_PLUGIN_VERSION', '2.0.37');
     29define('MUCHAT_AI_CHATBOT_PLUGIN_VERSION', '2.0.38');
    3030// define('MUCHAT_AI_CHATBOT_CACHE_DURATION', HOUR_IN_SECONDS);
    3131define('MUCHAT_AI_CHATBOT_PLUGIN_FILE', __FILE__);
  • muchat-ai/tags/2.0.38/readme.txt

    r3309949 r3330738  
    44Requires at least: 5.0
    55Tested up to: 6.8
    6 Stable tag: 2.0.37
     6Stable tag: 2.0.38
    77Requires PHP: 7.4
    88License: GPLv2 or later
     
    7676== Changelog ==
    7777
     78= 2.0.38 =
     79- Feature: Added an option for different initial messages for guest (non-logged-in) users.
     80- Performance: Major enhancements to API and admin pages.
     81- Performance: Optimized database queries for products, posts, and pages to reduce memory usage on large sites.
     82- Performance: Implemented caching for API token validation to prevent server slowdowns.
     83- Performance: Cached heavy queries on the plugin's settings page to improve load times.
     84- Bugfix: Corrected an "undefined variable" warning on the widget settings page.
     85
    7886= 2.0.37 =
    7987* Fixed issue with `<front>` tag being removed when saving display rules
  • muchat-ai/tags/2.0.38/templates/admin/settings.php

    r3300525 r3330738  
    5757        <?php submit_button(__('Save Changes', 'muchat-ai'), 'primary', 'submit', false, ['style' => 'margin-top: 20px;']); ?>
    5858    </form>
     59
     60    <form method="get" style="display: inline-block; margin-left: 10px; margin-top: 20px;">
     61        <input type="hidden" name="page" value="muchat-woocommerce-api">
     62        <input type="hidden" name="action" value="muchat_refresh_meta">
     63        <?php wp_nonce_field('muchat_refresh_meta_action'); ?>
     64        <?php submit_button(__('Refresh Fields', 'muchat-ai'), 'secondary', 'refresh_meta', false); ?>
     65    </form>
    5966</div>
  • muchat-ai/tags/2.0.38/templates/admin/widget-settings.php

    r3309949 r3330738  
    150150                                </td>
    151151                            </tr>
     152                             <tr valign="top">
     153                                <th scope="row"><?php esc_html_e('Different Messages for Guests', 'muchat-ai'); ?></th>
     154                                <td>
     155                                    <label>
     156                                        <input type="checkbox" id="muchat_ai_chatbot_enable_guest_messages" name="muchat_ai_chatbot_enable_guest_messages" value="1" <?php checked($enable_guest_messages, '1'); ?> />
     157                                        <?php esc_html_e('Enable different initial messages for non-logged-in users.', 'muchat-ai'); ?>
     158                                    </label>
     159                                </td>
     160                            </tr>
     161                            <tr valign="top" id="muchat-guest-initial-messages-row" style="display: none;">
     162                                <th scope="row"><?php esc_html_e('Guest Initial Messages', 'muchat-ai'); ?></th>
     163                                <td>
     164                                    <div id="muchat-guest-initial-messages-list"></div>
     165                                    <button type="button" class="button" id="muchat-add-guest-initial-message">
     166                                        <span class="dashicons dashicons-plus-alt2" style="vertical-align: text-bottom;"></span>
     167                                        <?php esc_html_e('Add Message', 'muchat-ai'); ?>
     168                                    </button>
     169                                    <input type="hidden" id="muchat_ai_chatbot_guest_initial_messages" name="muchat_ai_chatbot_guest_initial_messages" value="<?php echo esc_attr(is_array($guest_initial_messages) ? '' : $guest_initial_messages); ?>" />
     170                                    <p class="description">
     171                                        <?php esc_html_e('These messages will be shown to visitors who are not logged in. If left empty, the default messages above will be used.', 'muchat-ai'); ?>
     172                                    </p>
     173                                </td>
     174                            </tr>
    152175                            <tr valign="top">
    153176                                <th scope="row"><?php esc_html_e('Load Strategy', 'muchat-ai'); ?></th>
  • muchat-ai/trunk/assets/js/admin.js

    r3300525 r3330738  
    144144
    145145    $(document).ready(function() {
    146         // Initial Messages Management
    147         var container = document.getElementById('muchat-initial-messages-list');
    148         var addBtn = document.getElementById('muchat-add-initial-message');
    149         var hiddenInput = document.getElementById('muchat_ai_chatbot_interface_initial_messages');
    150        
    151         if (container && addBtn && hiddenInput) {
    152             var initial = [];
     146        // --- Reusable function for Initial Messages Management ---
     147        function initializeMessageList(containerId, buttonId, inputId) {
     148            var container = document.getElementById(containerId);
     149            var addBtn = document.getElementById(buttonId);
     150            var hiddenInput = document.getElementById(inputId);
     151
     152            if (!container || !addBtn || !hiddenInput) {
     153                return; // Exit if elements are not found
     154            }
     155
     156            var messages = [];
    153157
    154158            // Parse initial messages with error handling
     
    156160                if (hiddenInput.value && hiddenInput.value.trim() !== '') {
    157161                    try {
    158                         initial = JSON.parse(hiddenInput.value);
    159                         if (!Array.isArray(initial)) {
     162                        messages = JSON.parse(hiddenInput.value);
     163                        if (!Array.isArray(messages)) {
    160164                            console.warn('Initial messages not in expected array format, converting...');
    161                             initial = hiddenInput.value.split(',').map(function(x) {
     165                            messages = hiddenInput.value.split(',').map(function(x) {
    162166                                return x.trim();
    163167                            });
    164168                        }
    165169                    } catch (e) {
    166                         console.warn('Failed to parse JSON, falling back to comma separation', e);
    167                         initial = hiddenInput.value.split(',').map(function(x) {
     170                        console.warn('Failed to parse JSON, falling back to comma separation for ' + inputId, e);
     171                        messages = hiddenInput.value.split(',').map(function(x) {
    168172                            return x.trim();
    169173                        });
     
    171175                }
    172176            } catch (e) {
    173                 console.error('Error processing initial messages:', e);
    174                 initial = [];
     177                console.error('Error processing initial messages for ' + inputId + ':', e);
     178                messages = [];
    175179            }
    176180
    177181            function render() {
    178182                container.innerHTML = '';
    179                 initial.forEach(function(msg, idx) {
     183                messages.forEach(function(msg, idx) {
    180184                    var div = document.createElement('div');
    181185                    div.className = 'initial-message-row';
     
    183187                    input.type = 'text';
    184188                    input.className = 'regular-text';
    185                     input.value = msg || ''; // Prevent undefined values
     189                    input.value = msg || '';
    186190                    input.placeholder = 'Message...';
    187191                    input.oninput = function() {
    188                         initial[idx] = input.value;
     192                        messages[idx] = input.value;
    189193                        updateHidden();
    190194                    };
     
    195199                    remove.title = 'Remove message';
    196200                    remove.onclick = function() {
    197                         initial.splice(idx, 1);
     201                        messages.splice(idx, 1);
    198202                        render();
    199203                        updateHidden();
     
    208212            function updateHidden() {
    209213                try {
    210                     var filtered = initial.map(function(x) {
     214                    var filtered = messages.map(function(x) {
    211215                        return (x || '').trim();
    212216                    }).filter(Boolean);
    213217                    hiddenInput.value = JSON.stringify(filtered);
    214218                } catch (e) {
    215                     console.error('Error updating hidden input:', e);
     219                    console.error('Error updating hidden input for ' + inputId + ':', e);
    216220                }
    217221            }
    218222
    219223            addBtn.onclick = function() {
    220                 initial.push('');
     224                messages.push('');
    221225                render();
    222226            };
     
    224228            render();
    225229        }
     230       
     231        // Initialize both message lists
     232        initializeMessageList('muchat-initial-messages-list', 'muchat-add-initial-message', 'muchat_ai_chatbot_interface_initial_messages');
     233        initializeMessageList('muchat-guest-initial-messages-list', 'muchat-add-guest-initial-message', 'muchat_ai_chatbot_guest_initial_messages');
     234
     235        // --- Toggle for Guest Initial Messages ---
     236        var guestToggleCheckbox = document.getElementById('muchat_ai_chatbot_enable_guest_messages');
     237        var guestMessagesRow = document.getElementById('muchat-guest-initial-messages-row');
     238
     239        if (guestToggleCheckbox && guestMessagesRow) {
     240            function toggleGuestMessages() {
     241                guestMessagesRow.style.display = guestToggleCheckbox.checked ? 'table-row' : 'none';
     242            }
     243           
     244            // Set initial state on page load
     245            toggleGuestMessages();
     246
     247            // Add event listener for changes
     248            guestToggleCheckbox.addEventListener('change', toggleGuestMessages);
     249        }
     250
    226251
    227252        // Schedule Settings Toggle
  • muchat-ai/trunk/includes/Admin/Settings.php

    r3309949 r3330738  
    248248        $interface_primary_color = get_option('muchat_ai_chatbot_interface_primary_color', '#145dee');
    249249        $interface_initial_messages = get_option('muchat_ai_chatbot_interface_initial_messages', '');
     250        $enable_guest_messages = get_option('muchat_ai_chatbot_enable_guest_messages', '0');
     251        $guest_initial_messages = get_option('muchat_ai_chatbot_guest_initial_messages', '');
    250252        $load_strategy = get_option('muchat_ai_chatbot_load_strategy', 'FAST');
    251253        $use_logged_in_user_info = get_option('muchat_ai_chatbot_use_logged_in_user_info', '');
     
    544546    public function register_settings()
    545547    {
     548        // Handle cache clearing action
     549        if (
     550            isset($_GET['action']) &&
     551            $_GET['action'] === 'muchat_refresh_meta' &&
     552            isset($_GET['_wpnonce']) &&
     553            wp_verify_nonce(sanitize_key($_GET['_wpnonce']), 'muchat_refresh_meta_action')
     554        ) {
     555            delete_transient('muchat_product_meta_fields_cache');
     556            add_settings_error(
     557                'muchat_ai_chatbot_plugin_messages',
     558                'meta_cache_cleared',
     559                __('Product meta fields cache has been cleared successfully.', 'muchat-ai'),
     560                'updated'
     561            );
     562        }
     563
    546564        // Register plugin settings
    547565        $this->register_plugin_settings();
     
    550568        $this->register_plugin_onboarding();
    551569
    552         register_setting('muchat_ai_chatbot_plugin_settings', 'muchat_ai_chatbot_plugin_options', array(
    553             'sanitize_callback' => function ($input) {
    554                 // If input is not an array, return an empty array
    555                 if (!is_array($input)) {
    556                     return array();
     570        // Register the main settings group for the plugin
     571        register_setting(
     572            'muchat_ai_chatbot_plugin_settings',
     573            'muchat_ai_chatbot_plugin_options',
     574            array(
     575                'sanitize_callback' => function ($input) {
     576                    // If input is not an array, return an empty array
     577                    if (!is_array($input)) {
     578                        return array();
     579                    }
     580
     581                    // Sanitize meta field selections (array of values)
     582                    if (isset($input['product_meta_fields']) && is_array($input['product_meta_fields'])) {
     583                        $input['product_meta_fields'] = array_map('sanitize_text_field', $input['product_meta_fields']);
     584                    }
     585
     586                    // Sanitize meta labels (associative array)
     587                    if (isset($input['meta_labels']) && is_array($input['meta_labels'])) {
     588                        foreach ($input['meta_labels'] as $key => $value) {
     589                            $input['meta_labels'][$key] = sanitize_text_field($value);
     590                        }
     591                    }
     592
     593                    return $input;
    557594                }
    558 
    559                 // Sanitize meta field selections (array of values)
    560                 if (isset($input['product_meta_fields']) && is_array($input['product_meta_fields'])) {
    561                     $input['product_meta_fields'] = array_map('sanitize_text_field', $input['product_meta_fields']);
    562                 }
    563 
    564                 // Sanitize meta labels (associative array)
    565                 if (isset($input['meta_labels']) && is_array($input['meta_labels'])) {
    566                     foreach ($input['meta_labels'] as $key => $value) {
    567                         $input['meta_labels'][$key] = sanitize_text_field($value);
    568                     }
    569                 }
    570 
    571                 return $input;
    572             }
    573         ));
     595            )
     596        );
    574597
    575598        // Product Meta Section (in a separate page section)
     
    734757            'default' => ''
    735758        ));
     759        register_setting('muchat-settings-group', 'muchat_ai_chatbot_enable_guest_messages', array(
     760            'type' => 'string',
     761            'sanitize_callback' => 'sanitize_text_field',
     762            'default' => '0'
     763        ));
     764        register_setting('muchat-settings-group', 'muchat_ai_chatbot_guest_initial_messages', array(
     765            'type' => 'string',
     766            'sanitize_callback' => 'sanitize_textarea_field',
     767            'default' => ''
     768        ));
    736769        register_setting('muchat-settings-group', 'muchat_ai_chatbot_load_strategy', array(
    737770            'type' => 'string',
     
    806839        add_option('muchat_ai_chatbot_onboarding', false);
    807840
     841        // Add default options for the new guest message fields
     842        add_option('muchat_ai_chatbot_enable_guest_messages', '0');
     843        add_option('muchat_ai_chatbot_guest_initial_messages', '');
     844
    808845        // Default load strategy
    809846        add_option('muchat_ai_chatbot_load_strategy', 'FAST');
  • muchat-ai/trunk/includes/Api/Middleware/AuthMiddleware.php

    r3300525 r3330738  
    5050    private function validate_token($token)
    5151    {
     52        $transient_key = 'muchat_token_' . md5($token);
     53
     54        // Check cache first
     55        if (get_transient($transient_key)) {
     56            return true;
     57        }
     58
    5259        global $wp_filter;
    5360
     
    105112            }
    106113
     114            // If validation is successful, store it in cache for 1 hour
     115            set_transient($transient_key, true, HOUR_IN_SECONDS);
     116
    107117            return true;
    108118        } finally {
  • muchat-ai/trunk/includes/Core/Plugin.php

    r3300592 r3330738  
    1818     */
    1919    protected $version;
     20
     21    /**
     22     * Settings class instance
     23     *
     24     * @var \Muchat\Api\Admin\Settings
     25     */
     26    private $settings_instance;
    2027
    2128    /**
     
    5663
    5764    /**
     65     * Get (and instantiate if necessary) the settings admin class
     66     *
     67     * @return \Muchat\Api\Admin\Settings
     68     */
     69    public function get_settings_instance()
     70    {
     71        if (null === $this->settings_instance) {
     72            $this->settings_instance = new \Muchat\Api\Admin\Settings();
     73        }
     74        return $this->settings_instance;
     75    }
     76
     77    /**
    5878     * Register admin hooks
    5979     *
     
    6282    private function setup_admin()
    6383    {
    64         $admin = new \Muchat\Api\Admin\Settings();
    65 
    66         $this->loader->add_action('admin_menu', $admin, 'add_menu_page');
    67         $this->loader->add_action('admin_init', $admin, 'register_settings');
    68         $this->loader->add_action('admin_enqueue_scripts', $admin, 'enqueue_styles');
    69         $this->loader->add_action('admin_enqueue_scripts', $admin, 'enqueue_scripts');
     84        $this->loader->add_action('admin_menu', $this, 'add_admin_menu');
     85        $this->loader->add_action('admin_init', $this, 'register_admin_settings');
     86        $this->loader->add_action('admin_enqueue_scripts', $this, 'enqueue_admin_assets');
     87
     88    }
     89
     90   
     91    /**
     92     * Wrapper for adding the admin menu
     93     */
     94    public function add_admin_menu()
     95    {
     96        $this->get_settings_instance()->add_menu_page();
     97    }
     98
     99    /**
     100     * Wrapper for registering settings
     101     */
     102    public function register_admin_settings()
     103    {
     104        $this->get_settings_instance()->register_settings();
     105    }
     106
     107    /**
     108     * Wrapper for enqueuing admin assets
     109     *
     110     * @param string $hook The current admin page hook.
     111     */
     112    public function enqueue_admin_assets($hook)
     113    {
     114        $this->get_settings_instance()->enqueue_styles($hook);
     115        $this->get_settings_instance()->enqueue_scripts($hook);
    70116    }
    71117
  • muchat-ai/trunk/includes/Frontend/Widget.php

    r3300525 r3330738  
    8080    /**
    8181     * Determines if the chatbot should be displayed on the current page
    82      * based on visibility settings
     82     * based on visibility settings. This is the primary logic gate.
    8383     */
    8484    private function should_display()
    8585    {
    86         // Check if widget is enabled
    87         $widget_enabled = get_option('muchat_ai_chatbot_widget_enabled', '1');
    88         if ($widget_enabled !== '1') {
     86        // 1. Check if widget is globally enabled
     87        if (get_option('muchat_ai_chatbot_widget_enabled', '1') !== '1') {
    8988            return false;
    9089        }
    9190
    92         // Check scheduling if enabled
    93         $schedule_enabled = get_option('muchat_ai_chatbot_schedule_enabled', '0');
    94         if ($schedule_enabled === '1') {
     91        // 2. Check scheduling if enabled
     92        if (get_option('muchat_ai_chatbot_schedule_enabled', '0') === '1') {
    9593            if (!$this->is_within_schedule()) {
    9694                return false;
     
    9896        }
    9997
    100         // Get visibility settings
     98        // 3. Check page visibility rules
    10199        $visibility_mode = get_option('muchat_ai_chatbot_visibility_mode', 'all');
    102100        $visibility_pages = get_option('muchat_ai_chatbot_visibility_pages', '');
    103101
    104         // If visibility pages is empty...
    105         if (empty($visibility_pages)) {
    106             // For "show everywhere except" mode, show on all pages
    107             if ($visibility_mode === 'all') {
    108                 return true;
    109             }
    110             // For "only show on listed pages" mode, don't show anywhere if list is empty
    111             else {
    112                 return false;
    113             }
    114         }
    115 
    116         // If wildcard is specified, show everywhere for 'all' mode
    117         if (trim($visibility_pages) === '*') {
     102        // Rule: 'Disabled Everywhere'
     103        if ($visibility_mode === 'none') {
     104            return false;
     105        }
     106
     107        // Rule: 'Show on all pages' (and no exceptions are listed)
     108        if ($visibility_mode === 'all' && empty(trim($visibility_pages))) {
    118109            return true;
    119110        }
    120111
    121         // Get current path
    122         $current_path = isset($_SERVER['REQUEST_URI']) ? esc_url_raw(wp_unslash($_SERVER['REQUEST_URI'])) : '';
    123 
    124         // Check if this is the homepage
    125         $is_front = is_front_page() || $current_path === '/' || $current_path === '/index.php';
    126 
    127         // Convert path patterns to array
    128         $patterns = array_filter(explode("\n", $visibility_pages));
    129         $matches = false;
     112        // Rule: 'Only show on listed pages' (and the list is empty)
     113        if ($visibility_mode === 'specific' && empty(trim($visibility_pages))) {
     114            return false;
     115        }
     116
     117        // The logic now depends on matching the current page against the list.
     118        $matches = $this->is_path_match($visibility_pages);
     119
     120        // Rule: 'Show on all pages EXCEPT those listed'
     121        if ($visibility_mode === 'all') {
     122            return !$matches;
     123        }
     124
     125        // Rule: 'Only show on pages listed'
     126        if ($visibility_mode === 'specific') {
     127            return $matches;
     128        }
     129
     130        // Default fallback, should ideally not be reached.
     131        return false;
     132    }
     133
     134    /**
     135     * Checks if the current request path matches any of the given patterns.
     136     * Handles exact, wildcard, UTF-8, percent-encoded, and <front> patterns.
     137     *
     138     * @param string $patterns_string A newline-separated string of URL patterns.
     139     * @return bool True if the path matches one of the patterns, false otherwise.
     140     */
     141    private function is_path_match($patterns_string)
     142    {
     143        // 1. Get and normalize the current page's path.
     144        // We get the raw URI, strip any query parameters, and then decode it.
     145        $request_uri = isset($_SERVER['REQUEST_URI']) ? wp_unslash($_SERVER['REQUEST_URI']) : '';
     146        $path_only = strtok($request_uri, '?');
     147        $current_path = rtrim(urldecode($path_only), '/');
     148
     149        // Treat an empty path (which can be the homepage) as '/'.
     150        if (empty($current_path)) {
     151            $current_path = '/';
     152        }
     153
     154        // 2. Use WordPress's reliable function to determine if it's the front page.
     155        $is_front = is_front_page();
     156
     157        // 3. Handle the global wildcard match immediately.
     158        if (trim($patterns_string) === '*') {
     159            return true;
     160        }
     161
     162        // 4. Process the list of patterns.
     163        $patterns = explode("\n", $patterns_string);
    130164
    131165        foreach ($patterns as $pattern) {
    132166            $pattern = trim($pattern);
    133 
    134             // Skip empty lines
    135167            if (empty($pattern)) {
    136168                continue;
    137169            }
    138170
    139             // Check for front page
    140             if ($pattern === '<front>' && $is_front) {
    141                 $matches = true;
    142                 break;
    143             }
    144 
    145             // Check for exact matches
    146             if ($pattern === $current_path) {
    147                 $matches = true;
    148                 break;
    149             }
    150 
    151             // Check for wildcard matches
    152             if (strpos($pattern, '*') !== false) {
    153                 $regex_pattern = '@^' . str_replace('*', '.*', $pattern) . '$@';
    154                 if (preg_match($regex_pattern, $current_path)) {
    155                     $matches = true;
    156                     break;
     171            // Also decode the user-provided pattern in case it's percent-encoded.
     172            $decoded_pattern = urldecode($pattern);
     173
     174            // A. Check for the special '<front>' tag.
     175            if ($decoded_pattern === '<front>') {
     176                if ($is_front) {
     177                    return true; // Match found.
    157178                }
    158             }
    159         }
    160 
    161         // Return based on visibility mode
    162         if ($visibility_mode === 'all') {
    163             // Show on all pages EXCEPT those listed
    164             return !$matches;
    165         } else {
    166             // Only show on pages listed
    167             return $matches;
    168         }
     179                continue; // Not the front page, so check next pattern.
     180            }
     181
     182            // B. Normalize the pattern for comparison.
     183            $normalized_pattern = rtrim($decoded_pattern, '/');
     184            if (empty($normalized_pattern)) {
     185                $normalized_pattern = '/';
     186            }
     187
     188            // C. Check for wildcard matches.
     189            if (strpos($normalized_pattern, '*') !== false) {
     190                // Escape regex characters, then replace our wildcard `*` with `.*`.
     191                // The 'u' modifier is crucial for correct UTF-8 pattern matching.
     192                $regex = '@^' . str_replace('\*', '.*', preg_quote($normalized_pattern, '@')) . '$@u';
     193                if (preg_match($regex, $current_path)) {
     194                    return true; // Match found.
     195                }
     196            }
     197            // D. Check for exact matches (only if no wildcard).
     198            else {
     199                if ($normalized_pattern === $current_path) {
     200                    return true; // Match found.
     201                }
     202                // Also check if the pattern is for the homepage and it is the homepage
     203                if ($normalized_pattern === '/' && $is_front) {
     204                    return true;
     205                }
     206            }
     207        }
     208
     209        // No patterns matched the current path.
     210        return false;
    169211    }
    170212
     
    341383
    342384    /**
    343      * Process and get initial messages
     385     * Get initial messages based on user login status
     386     *
     387     * @return array
    344388     */
    345389    private function get_initial_messages()
    346390    {
    347         $interface_initial_messages = get_option('muchat_ai_chatbot_interface_initial_messages', '');
    348         if (empty($interface_initial_messages)) {
    349             return [];
    350         }
    351 
    352         // Parse initial messages
    353         $initial_messages = [];
     391        // Get guest-specific message settings
     392        $enable_guest_messages = get_option('muchat_ai_chatbot_enable_guest_messages', '0');
     393        $guest_initial_messages_json = get_option('muchat_ai_chatbot_guest_initial_messages', '');
     394
     395        // Check if we should use guest messages
     396        if ('1' === $enable_guest_messages && !is_user_logged_in() && !empty($guest_initial_messages_json)) {
     397            try {
     398                $guest_messages = json_decode($guest_initial_messages_json, true);
     399                if (is_array($guest_messages) && !empty($guest_messages)) {
     400                    return $guest_messages;
     401                }
     402            } catch (\Exception $e) {
     403                // If JSON is invalid, fall through to default messages
     404            }
     405        }
     406
     407        // --- Fallback to default messages ---
     408        $initial_messages_json = get_option('muchat_ai_chatbot_interface_initial_messages', '');
     409        if (empty($initial_messages_json)) {
     410            return []; // No messages configured
     411        }
     412
    354413        try {
    355             $decoded = json_decode($interface_initial_messages, true);
    356             if (is_array($decoded)) {
    357                 $initial_messages = $decoded;
    358             } else {
    359                 // Fallback to comma-separated
    360                 $initial_messages = array_map('trim', explode(',', $interface_initial_messages));
     414            $initial_messages = json_decode($initial_messages_json, true);
     415            // Ensure it's a non-empty array
     416            if (is_array($initial_messages) && !empty($initial_messages)) {
     417                return $initial_messages;
    361418            }
    362419        } catch (\Exception $e) {
    363             // Fallback to empty array if parsing fails
    364             return [];
    365         }
    366 
    367         // Replace variables
    368         $firstName = '';
    369         $lastName = '';
    370         if (is_user_logged_in()) {
    371             $current_user = wp_get_current_user();
    372             $firstName = $current_user->user_firstname;
    373             $lastName = $current_user->user_lastname;
    374         } else {
    375             $firstName = get_option('muchat_ai_chatbot_contact_first_name', '');
    376             $lastName = get_option('muchat_ai_chatbot_contact_last_name', '');
    377         }
    378 
    379         foreach ($initial_messages as &$msg) {
    380             $msg = str_replace(['$name', '$lastname'], [$firstName, $lastName], $msg);
    381         }
    382 
    383         return array_filter($initial_messages);
     420            // JSON might be malformed, or it might be a simple comma-separated string
     421        }
     422
     423        // Fallback for old comma-separated format
     424        $messages = array_map('trim', explode(',', $initial_messages_json));
     425        return array_filter($messages);
    384426    }
    385427
  • muchat-ai/trunk/includes/Models/Page.php

    r3300525 r3330738  
    6363        }
    6464
    65         // Get all valid page IDs first (for accurate counting and filtering)
    66         $all_pages_query = new \WP_Query(array_merge(
    67             $args,
    68             ['posts_per_page' => -1]
    69         ));
    70 
    71         // Filter to only get valid pages
    72         $valid_page_ids = [];
    73         foreach ($all_pages_query->posts as $page_id) {
    74             $page = get_post($page_id);
    75             if ($this->is_valid_page($page)) {
    76                 $valid_page_ids[] = $page_id;
    77             }
    78         }
    79 
    80         // Get total count from valid pages
    81         $total_count = count($valid_page_ids);
    82 
    83         // Get requested pages (with pagination)
     65        // Get pages to exclude
     66        $pages_to_exclude = $this->get_pages_to_exclude();
     67        if (!empty($pages_to_exclude)) {
     68            $args['post__not_in'] = $pages_to_exclude;
     69        }
     70
     71        // Add a WHERE clause to filter out empty content
     72        add_filter('posts_where', function ($where) {
     73            global $wpdb;
     74            $where .= " AND {$wpdb->posts}.post_content != ''";
     75            return $where;
     76        }, 10, 1);
     77
     78
     79        // Set pagination parameters
    8480        $requested_offset = $params['skip'] ?? 0;
    8581        $requested_limit = $params['take'] ?? 10;
    8682
    87         // Get the slice of page IDs for this request
    88         $paginated_ids = array_slice($valid_page_ids, $requested_offset, $requested_limit);
     83        $args['posts_per_page'] = $requested_limit;
     84        $args['offset'] = $requested_offset;
     85
     86        // Execute the final query with pagination
     87        $query = new \WP_Query($args);
     88
     89        // Get total count from the same query
     90        $total_count = (int) $query->found_posts;
    8991
    9092        // Format the pages
    91         $pages = [];
    92         foreach ($paginated_ids as $page_id) {
    93             $page = get_post($page_id);
    94             $pages[] = $this->format_page($page);
    95         }
     93        $pages = array_map(function ($page_id) {
     94            return $this->format_page(get_post($page_id));
     95        }, $query->posts);
     96
     97        // Remove the temporary WHERE filter to avoid affecting other queries
     98        remove_filter('posts_where', '__return_true');
     99
    96100
    97101        $plugin = new \Muchat\Api\Core\Plugin();
     
    108112
    109113    /**
    110      * Check if a page is valid (not a WooCommerce page and has content)
    111      *
    112      * @param \WP_Post $page
    113      * @return bool
    114      */
    115     private function is_valid_page($page)
    116     {
    117         if (empty($page) || empty($page->post_content)) {
    118             return false;
    119         }
    120 
     114     * Get an array of page IDs to exclude
     115     *
     116     * @return array
     117     */
     118    private function get_pages_to_exclude()
     119    {
    121120        $woocommerce_pages = [];
    122 
    123121        // Only check WooCommerce pages if WooCommerce is active
    124122        if (class_exists('WooCommerce')) {
    125             // Main WooCommerce pages
    126             $woocommerce_pages = [
     123            $woocommerce_pages = array_filter([
    127124                wc_get_page_id('cart'),
    128125                wc_get_page_id('checkout'),
     
    130127                wc_get_page_id('shop'),
    131128                wc_get_page_id('terms'),
    132             ];
    133         }
    134 
     129            ]);
     130        }
    135131        // Additional pages to exclude regardless of WooCommerce
    136         $additional_pages = [
    137             // Check by slug (different variations)
     132        $additional_pages = array_filter([
    138133            $this->get_page_id_by_slug('wishlist'),
    139134            $this->get_page_id_by_slug('compare'),
     
    142137            $this->get_page_id_by_slug('my-wishlist'),
    143138            $this->get_page_id_by_slug('my-compare'),
    144 
    145             // Check by title (different variations)
    146139            $this->get_page_id_by_title('Wishlist'),
    147140            $this->get_page_id_by_title('Compare'),
     
    151144            $this->get_page_id_by_title('My Compare'),
    152145            $this->get_page_id_by_title('علاقه‌مندی‌ها'), // For Persian sites
    153             $this->get_page_id_by_title('مقایسه')        // For Persian sites
    154         ];
    155 
    156         $pages_to_exclude = array_merge($woocommerce_pages, $additional_pages);
     146            $this->get_page_id_by_title('مقایسه') // For Persian sites
     147        ]);
     148        return array_unique(array_merge($woocommerce_pages, $additional_pages));
     149    }
     150
     151
     152    /**
     153     * Check if a page is valid (not a WooCommerce page and has content)
     154     *
     155     * @param \WP_Post $page
     156     * @return bool
     157     */
     158    private function is_valid_page($page)
     159    {
     160        if (empty($page) || empty($page->post_content)) {
     161            return false;
     162        }
     163
     164        $pages_to_exclude = $this->get_pages_to_exclude();
    157165
    158166        // If the page is one of the excluded system pages, return false
     
    208216        }
    209217
    210         $woocommerce_pages = [];
    211 
    212         // Only check WooCommerce pages if WooCommerce is active
    213         if (class_exists('WooCommerce')) {
    214             // Main WooCommerce pages
    215             $woocommerce_pages = [
    216                 wc_get_page_id('cart'),
    217                 wc_get_page_id('checkout'),
    218                 wc_get_page_id('myaccount'),
    219                 wc_get_page_id('shop'),
    220                 wc_get_page_id('terms'),
    221             ];
    222         }
    223 
    224         // Additional pages to exclude regardless of WooCommerce
    225         $additional_pages = [
    226             // Check by slug (different variations)
    227             $this->get_page_id_by_slug('wishlist'),
    228             $this->get_page_id_by_slug('compare'),
    229             $this->get_page_id_by_slug('wish-list'),
    230             $this->get_page_id_by_slug('compare-list'),
    231             $this->get_page_id_by_slug('my-wishlist'),
    232             $this->get_page_id_by_slug('my-compare'),
    233 
    234             // Check by title (different variations)
    235             $this->get_page_id_by_title('Wishlist'),
    236             $this->get_page_id_by_title('Compare'),
    237             $this->get_page_id_by_title('Wish List'),
    238             $this->get_page_id_by_title('Compare List'),
    239             $this->get_page_id_by_title('My Wishlist'),
    240             $this->get_page_id_by_title('My Compare'),
    241             $this->get_page_id_by_title('علاقه‌مندی‌ها'), // For Persian sites
    242             $this->get_page_id_by_title('مقایسه')        // For Persian sites
    243         ];
    244 
    245         $pages_to_exclude = array_merge($woocommerce_pages, $additional_pages);
     218        $pages_to_exclude = $this->get_pages_to_exclude();
    246219
    247220        // If the page is one of the excluded system pages, return null
  • muchat-ai/trunk/includes/Models/Post.php

    r3300525 r3330738  
    6363        }
    6464
    65         // Get all valid post IDs first (for accurate counting and filtering)
    66         $all_posts_query = new \WP_Query(array_merge(
    67             $args,
    68             ['posts_per_page' => -1]
    69         ));
    70 
    71         // Filter to only get valid posts
    72         $valid_post_ids = [];
    73         foreach ($all_posts_query->posts as $post_id) {
    74             $post = get_post($post_id);
    75             if ($this->is_valid_post($post)) {
    76                 $valid_post_ids[] = $post_id;
    77             }
    78         }
    79 
    80         // Get total count from valid posts
    81         $total_count = count($valid_post_ids);
    82 
    83         // Get requested posts (with pagination)
     65        // Add a meta query to exclude posts with empty content
     66        $args['meta_query'] = [
     67            [
     68                'key' => '_wp_old_slug', // A non-existent key to satisfy the structure, the real filtering is done by the where filter.
     69            ],
     70        ];
     71
     72        // Add a WHERE clause to filter out empty content. This is more efficient than a meta_query on post_content.
     73        add_filter('posts_where', function ($where) {
     74            global $wpdb;
     75            $where .= " AND {$wpdb->posts}.post_content != ''";
     76            return $where;
     77        });
     78
     79        // Set pagination parameters
    8480        $requested_offset = $params['skip'] ?? 0;
    8581        $requested_limit = $params['take'] ?? 10;
    8682
    87         // Get the slice of post IDs for this request
    88         $paginated_ids = array_slice($valid_post_ids, $requested_offset, $requested_limit);
     83        // Update the query with pagination parameters
     84        $args['posts_per_page'] = $requested_limit;
     85        $args['offset'] = $requested_offset;
     86
     87        // Execute the final query with pagination
     88        $query = new \WP_Query($args);
     89
     90        // Get total count from the same query
     91        $total_count = (int) $query->found_posts;
    8992
    9093        // Format the posts
    91         $posts = [];
    92         foreach ($paginated_ids as $post_id) {
    93             $post = get_post($post_id);
    94             $posts[] = $this->format_post($post);
    95         }
     94        $posts = array_map(function ($post_id) {
     95            return $this->format_post(get_post($post_id));
     96        }, $query->posts);
     97
    9698
    9799        $plugin = new \Muchat\Api\Core\Plugin();
  • muchat-ai/trunk/includes/Models/Product.php

    r3300525 r3330738  
    8181        }
    8282
    83         // Get all valid product IDs first (for accurate counting and filtering)
    84         $all_products_query = new \WP_Query(array_merge(
    85             $args,
    86             ['posts_per_page' => -1]
    87         ));
    88 
    89         // Get total count from valid products
    90         $total_count = count($all_products_query->posts);
    91 
    92         // Get requested products (with pagination)
     83        // Set pagination parameters
    9384        $requested_offset = $params['skip'] ?? 0;
    9485        $requested_limit = $params['take'] ?? 30;
     
    10091        // Execute the final query with pagination
    10192        $query = new \WP_Query($args);
     93
     94        // Get total count from the same query
     95        $total_count = (int) $query->found_posts;
    10296
    10397        // Format the products
     
    657651    public function get_product_meta_fields()
    658652    {
     653        $cache_key = 'muchat_product_meta_fields_cache';
     654        $cached_fields = get_transient($cache_key);
     655
     656        if (false !== $cached_fields) {
     657            return $cached_fields;
     658        }
     659
    659660        global $wpdb;
    660661
     
    768769        });
    769770
    770         return $meta_fields;
    771     }
    772 
    773     /**
    774      * Get sample values for a meta key
     771        // Combine standard meta fields and ACF fields
     772        $all_fields = array_values($meta_fields);
     773
     774        // Store the result in cache for 6 hours
     775        set_transient($cache_key, $all_fields, 6 * HOUR_IN_SECONDS);
     776
     777        return $all_fields;
     778    }
     779
     780    /**
     781     * Get sample values for a meta key from the database
    775782     *
    776783     * @param string $meta_key
  • muchat-ai/trunk/muchat-ai.php

    r3309949 r3330738  
    55 * Plugin URI: https://mu.chat
    66 * Description: Muchat, a powerful tool for customer support using artificial intelligence
    7  * Version: 2.0.37
     7 * Version: 2.0.38
    88 * Author: Muchat
    99 * Text Domain: muchat-ai
     
    2727
    2828// Define plugin constants with unique prefix
    29 define('MUCHAT_AI_CHATBOT_PLUGIN_VERSION', '2.0.37');
     29define('MUCHAT_AI_CHATBOT_PLUGIN_VERSION', '2.0.38');
    3030// define('MUCHAT_AI_CHATBOT_CACHE_DURATION', HOUR_IN_SECONDS);
    3131define('MUCHAT_AI_CHATBOT_PLUGIN_FILE', __FILE__);
  • muchat-ai/trunk/readme.txt

    r3309949 r3330738  
    44Requires at least: 5.0
    55Tested up to: 6.8
    6 Stable tag: 2.0.37
     6Stable tag: 2.0.38
    77Requires PHP: 7.4
    88License: GPLv2 or later
     
    7676== Changelog ==
    7777
     78= 2.0.38 =
     79- Feature: Added an option for different initial messages for guest (non-logged-in) users.
     80- Performance: Major enhancements to API and admin pages.
     81- Performance: Optimized database queries for products, posts, and pages to reduce memory usage on large sites.
     82- Performance: Implemented caching for API token validation to prevent server slowdowns.
     83- Performance: Cached heavy queries on the plugin's settings page to improve load times.
     84- Bugfix: Corrected an "undefined variable" warning on the widget settings page.
     85
    7886= 2.0.37 =
    7987* Fixed issue with `<front>` tag being removed when saving display rules
  • muchat-ai/trunk/templates/admin/settings.php

    r3300525 r3330738  
    5757        <?php submit_button(__('Save Changes', 'muchat-ai'), 'primary', 'submit', false, ['style' => 'margin-top: 20px;']); ?>
    5858    </form>
     59
     60    <form method="get" style="display: inline-block; margin-left: 10px; margin-top: 20px;">
     61        <input type="hidden" name="page" value="muchat-woocommerce-api">
     62        <input type="hidden" name="action" value="muchat_refresh_meta">
     63        <?php wp_nonce_field('muchat_refresh_meta_action'); ?>
     64        <?php submit_button(__('Refresh Fields', 'muchat-ai'), 'secondary', 'refresh_meta', false); ?>
     65    </form>
    5966</div>
  • muchat-ai/trunk/templates/admin/widget-settings.php

    r3309949 r3330738  
    150150                                </td>
    151151                            </tr>
     152                             <tr valign="top">
     153                                <th scope="row"><?php esc_html_e('Different Messages for Guests', 'muchat-ai'); ?></th>
     154                                <td>
     155                                    <label>
     156                                        <input type="checkbox" id="muchat_ai_chatbot_enable_guest_messages" name="muchat_ai_chatbot_enable_guest_messages" value="1" <?php checked($enable_guest_messages, '1'); ?> />
     157                                        <?php esc_html_e('Enable different initial messages for non-logged-in users.', 'muchat-ai'); ?>
     158                                    </label>
     159                                </td>
     160                            </tr>
     161                            <tr valign="top" id="muchat-guest-initial-messages-row" style="display: none;">
     162                                <th scope="row"><?php esc_html_e('Guest Initial Messages', 'muchat-ai'); ?></th>
     163                                <td>
     164                                    <div id="muchat-guest-initial-messages-list"></div>
     165                                    <button type="button" class="button" id="muchat-add-guest-initial-message">
     166                                        <span class="dashicons dashicons-plus-alt2" style="vertical-align: text-bottom;"></span>
     167                                        <?php esc_html_e('Add Message', 'muchat-ai'); ?>
     168                                    </button>
     169                                    <input type="hidden" id="muchat_ai_chatbot_guest_initial_messages" name="muchat_ai_chatbot_guest_initial_messages" value="<?php echo esc_attr(is_array($guest_initial_messages) ? '' : $guest_initial_messages); ?>" />
     170                                    <p class="description">
     171                                        <?php esc_html_e('These messages will be shown to visitors who are not logged in. If left empty, the default messages above will be used.', 'muchat-ai'); ?>
     172                                    </p>
     173                                </td>
     174                            </tr>
    152175                            <tr valign="top">
    153176                                <th scope="row"><?php esc_html_e('Load Strategy', 'muchat-ai'); ?></th>
Note: See TracChangeset for help on using the changeset viewer.