%s
', sprintf( /* translators: %1$s: minimum PHP version required, %2$s: current PHP version */ esc_html__( 'Agentorie Chat requires PHP %1$s or higher. Your server is running PHP %2$s.', 'agentorie-chat' ), esc_html( AGENTORIE_CHAT_MIN_PHP ), esc_html( PHP_VERSION ) ) ); } /** * Initialize the plugin. * * @since 1.0.0 * @return void */ function agentorie_chat_init() { if ( ! agentorie_chat_check_php_version() ) { return; } // Translations are automatically loaded by WordPress.org since WP 4.6. require_once AGENTORIE_CHAT_PLUGIN_DIR . 'includes/class-agentorie-logger.php'; require_once AGENTORIE_CHAT_PLUGIN_DIR . 'includes/class-agentorie-database.php'; require_once AGENTORIE_CHAT_PLUGIN_DIR . 'includes/class-agentorie-session.php'; require_once AGENTORIE_CHAT_PLUGIN_DIR . 'includes/class-agentorie-api.php'; require_once AGENTORIE_CHAT_PLUGIN_DIR . 'includes/class-agentorie-webhook.php'; require_once AGENTORIE_CHAT_PLUGIN_DIR . 'includes/class-agentorie-gdpr.php'; require_once AGENTORIE_CHAT_PLUGIN_DIR . 'includes/class-agentorie-content-indexer.php'; require_once AGENTORIE_CHAT_PLUGIN_DIR . 'includes/class-agentorie-admin.php'; require_once AGENTORIE_CHAT_PLUGIN_DIR . 'includes/class-agentorie-frontend.php'; Agentorie_Database::get_instance(); Agentorie_Session::get_instance(); Agentorie_API::get_instance(); Agentorie_Webhook::get_instance(); Agentorie_GDPR::init(); // Initialize content indexer if enabled. $indexer = Agentorie_Content_Indexer::get_instance(); $indexer->init(); if ( is_admin() ) { Agentorie_Admin::get_instance(); } if ( ! is_admin() || wp_doing_ajax() ) { Agentorie_Frontend::get_instance(); } } add_action( 'plugins_loaded', 'agentorie_chat_init' ); /** * Clear third-party caches. * * Called on plugin activation, update, and settings changes. * All cache clearing is wrapped defensively to prevent fatal errors. * * This function clears page caches and minified assets to ensure * the chat widget displays correctly after updates. It avoids * wp_cache_flush() to prevent performance impact on the entire site. * * @since 1.0.0 * @return void */ function agentorie_chat_clear_third_party_caches() { // ========================================= // SPECIFIC PLUGINS (known implementations) // ========================================= // SiteGround Optimizer. if ( function_exists( 'sg_cachepress_purge_cache' ) ) { sg_cachepress_purge_cache(); } // WP Super Cache. if ( function_exists( 'wp_cache_clear_cache' ) ) { wp_cache_clear_cache(); } // W3 Total Cache. if ( function_exists( 'w3tc_flush_all' ) ) { w3tc_flush_all(); } // LiteSpeed Cache. if ( class_exists( 'LiteSpeed_Cache_API' ) && method_exists( 'LiteSpeed_Cache_API', 'purge_all' ) ) { LiteSpeed_Cache_API::purge_all(); } // WP Rocket - Use targeted clearing instead of full domain purge. if ( function_exists( 'rocket_clean_minify' ) ) { rocket_clean_minify(); // Clear minified CSS/JS only. } if ( function_exists( 'rocket_clean_domain' ) ) { rocket_clean_domain(); // Clear page cache. } // WP Fastest Cache. if ( function_exists( 'wpfc_clear_all_cache' ) ) { wpfc_clear_all_cache(); } // Autoptimize - CSS/JS only. if ( class_exists( 'autoptimizeCache' ) && method_exists( 'autoptimizeCache', 'clearall' ) ) { autoptimizeCache::clearall(); } // Hummingbird - defensive check for module and method. if ( class_exists( 'Hummingbird\\Core\\Utils' ) && method_exists( 'Hummingbird\\Core\\Utils', 'get_module' ) ) { try { $module = Hummingbird\Core\Utils::get_module( 'page_cache' ); if ( is_object( $module ) && method_exists( $module, 'clear_cache' ) ) { $module->clear_cache(); } } catch ( \Throwable $e ) { // Silently fail - cache clearing is not critical. unset( $e ); } } // Cache Enabler. if ( class_exists( 'Cache_Enabler' ) && method_exists( 'Cache_Enabler', 'clear_total_cache' ) ) { Cache_Enabler::clear_total_cache(); } // Breeze (Cloudways). if ( class_exists( 'Breeze_PurgeCache' ) && method_exists( 'Breeze_PurgeCache', 'breeze_cache_flush' ) ) { Breeze_PurgeCache::breeze_cache_flush(); } // Swift Performance. if ( class_exists( 'Swift_Performance_Cache' ) && method_exists( 'Swift_Performance_Cache', 'clear_all_cache' ) ) { Swift_Performance_Cache::clear_all_cache(); } // Comet Cache. if ( class_exists( 'comet_cache' ) && method_exists( 'comet_cache', 'clear' ) ) { comet_cache::clear(); } // Nginx Helper (for Nginx FastCGI cache). if ( function_exists( 'nginx_helper_purge_all' ) ) { nginx_helper_purge_all(); } // ========================================= // ADDITIONAL PLUGINS (direct API calls) // ========================================= // Cachify. if ( class_exists( 'Cachify' ) && method_exists( 'Cachify', 'flush_total_cache' ) ) { Cachify::flush_total_cache(); } // Hyper Cache. if ( function_exists( 'hyper_cache_invalidate' ) ) { hyper_cache_invalidate(); } // ========================================= // EXTENSIBILITY HOOK (prefixed) // ========================================= // Custom cache plugins can hook into this. // Example: add_action( 'agentorie_chat_clear_cache', 'my_custom_cache_clear' ). do_action( 'agentorie_chat_clear_cache' ); // ========================================= // HOSTING-LEVEL CACHES // ========================================= // WP Engine (hosting-level cache). if ( class_exists( 'WpeCommon' ) ) { if ( method_exists( 'WpeCommon', 'purge_memcached' ) ) { WpeCommon::purge_memcached(); } if ( method_exists( 'WpeCommon', 'purge_varnish_cache' ) ) { WpeCommon::purge_varnish_cache(); } } // Kinsta (hosting-level cache) - defensive property check. if ( class_exists( 'Kinsta\\Cache' ) && method_exists( 'Kinsta\\Cache', 'instance' ) ) { try { $kinsta_cache = Kinsta\Cache::instance(); if ( is_object( $kinsta_cache ) && isset( $kinsta_cache->kinsta_cache_purge ) && is_object( $kinsta_cache->kinsta_cache_purge ) && method_exists( $kinsta_cache->kinsta_cache_purge, 'purge_complete_caches' ) ) { $kinsta_cache->kinsta_cache_purge->purge_complete_caches(); } } catch ( \Throwable $e ) { // Silently fail - cache clearing is not critical. unset( $e ); } } // Pantheon (hosting-level cache). if ( function_exists( 'pantheon_wp_clear_edge_all' ) ) { pantheon_wp_clear_edge_all(); } // Cloudflare: The official Cloudflare plugin automatically purges cache // on WordPress content changes. For manual purge, users can hook into // the 'agentorie_chat_clear_cache' action above. // ========================================= // CLEAR OUR OWN TRANSIENTS ONLY // ========================================= // Instead of wp_cache_flush() which clears everything, // we only clear our own cached data. delete_transient( 'agentorie_chat_license_cache' ); delete_transient( 'agentorie_chat_config_cache' ); } /** * Plugin activation. * * @since 1.0.0 * @return void */ function agentorie_chat_activate() { if ( ! agentorie_chat_check_php_version() ) { deactivate_plugins( plugin_basename( __FILE__ ) ); wp_die( /* translators: %s: minimum PHP version required */ sprintf( esc_html__( 'Agentorie Chat requires PHP %s or higher.', 'agentorie-chat' ), esc_html( AGENTORIE_CHAT_MIN_PHP ) ), esc_html__( 'Plugin Activation Error', 'agentorie-chat' ), array( 'back_link' => true ) ); } require_once AGENTORIE_CHAT_PLUGIN_DIR . 'includes/class-agentorie-logger.php'; require_once AGENTORIE_CHAT_PLUGIN_DIR . 'includes/class-agentorie-database.php'; Agentorie_Database::create_tables(); agentorie_chat_set_default_options(); if ( ! wp_next_scheduled( 'agentorie_chat_cleanup_sessions' ) ) { wp_schedule_event( time(), 'hourly', 'agentorie_chat_cleanup_sessions' ); } if ( ! wp_next_scheduled( 'agentorie_chat_check_license' ) ) { wp_schedule_event( time(), 'twicedaily', 'agentorie_chat_check_license' ); } set_transient( 'agentorie_chat_activated', true, 30 ); agentorie_chat_clear_third_party_caches(); Agentorie_Logger::log_activation(); } register_activation_hook( __FILE__, 'agentorie_chat_activate' ); /** * Handle plugin upgrade. * * Clear caches on plugin update. * * @since 1.0.0 * @param WP_Upgrader $upgrader_object Upgrader instance. * @param array $options Upgrade options. * @return void */ function agentorie_chat_on_upgrade( $upgrader_object, $options ) { if ( isset( $options['plugins'] ) && is_array( $options['plugins'] ) ) { foreach ( $options['plugins'] as $plugin ) { if ( plugin_basename( __FILE__ ) === $plugin ) { agentorie_chat_clear_third_party_caches(); break; } } } } add_action( 'upgrader_process_complete', 'agentorie_chat_on_upgrade', 10, 2 ); /** * Plugin deactivation. * * @since 1.0.0 * @return void */ function agentorie_chat_deactivate() { if ( ! class_exists( 'Agentorie_Logger' ) ) { require_once AGENTORIE_CHAT_PLUGIN_DIR . 'includes/class-agentorie-logger.php'; } Agentorie_Logger::log_deactivation(); wp_clear_scheduled_hook( 'agentorie_chat_cleanup_sessions' ); wp_clear_scheduled_hook( 'agentorie_chat_sync_to_saas' ); wp_clear_scheduled_hook( 'agentorie_chat_check_license' ); wp_clear_scheduled_hook( 'agentorie_chat_daily_index' ); delete_transient( 'agentorie_chat_admin_presence' ); global $wpdb; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter $wpdb->query( "DELETE FROM {$wpdb->options} WHERE option_name LIKE '_transient_agentorie_chat_%' OR option_name LIKE '_transient_timeout_agentorie_chat_%'" ); } register_deactivation_hook( __FILE__, 'agentorie_chat_deactivate' ); /** * Set default options. * * @since 1.0.0 * @return void */ function agentorie_chat_set_default_options() { $default_values = array( 'enabled' => false, 'license_key' => '', 'license_status' => 'inactive', 'license_features' => array( 'base_chat' ), 'bot_name' => 'Stella', 'bot_subtitle' => 'Virtual Assistant', 'bot_avatar' => 'S', 'welcome_title' => 'Hello', 'welcome_message' => 'I am here to answer your questions.', 'admin_name' => 'Advisor', 'admin_avatar' => '', 'livechat_timeout_minutes' => 30, 'livechat_auto_release_hours' => 2, 'company_name' => '', 'company_email' => get_option( 'admin_email' ), 'company_phone' => '', 'company_address' => '', 'company_hours' => '', 'company_website' => home_url(), 'company_description' => '', 'widget_position' => 'bottom-right', 'widget_primary_color' => '#6366f1', 'widget_text_color' => '#ffffff', 'widget_show_on_mobile' => true, 'widget_delay_seconds' => 0, 'widget_excluded_pages' => array(), 'voice_enabled' => false, 'speech_to_text' => false, 'text_to_speech' => false, 'default_language' => 'en-US', 'conversation_retention_hours' => 6, 'monthly_limit' => 100, 'monthly_usage' => 0, 'monthly_reset_date' => '', 'notification_email' => get_option( 'admin_email' ), 'email_on_contact' => true, 'email_on_human_request' => true, 'debug_mode' => false, ); if ( false === get_option( 'agentorie_chat_settings' ) ) { add_option( 'agentorie_chat_settings', $default_values ); } } /** * Get a setting. * * @since 1.0.0 * @param string $key Setting key. * @param mixed $default_value Default value. * @return mixed Setting value. */ function agentorie_chat_get_setting( $key, $default_value = null ) { global $agentorie_chat_settings_cache; if ( null === $agentorie_chat_settings_cache ) { $agentorie_chat_settings_cache = get_option( 'agentorie_chat_settings', array() ); } return isset( $agentorie_chat_settings_cache[ $key ] ) ? $agentorie_chat_settings_cache[ $key ] : $default_value; } /** * Clear the settings cache. * * Forces the next agentorie_chat_get_setting() call to reload from database. * Called automatically by agentorie_chat_update_setting(). * * @since 1.2.0 * @return void */ function agentorie_chat_clear_settings_cache() { global $agentorie_chat_settings_cache; $agentorie_chat_settings_cache = null; } /** * Update a setting. * * @since 1.0.0 * @param string $key Setting key. * @param mixed $value Setting value. * @return bool True on success. */ function agentorie_chat_update_setting( $key, $value ) { $settings = get_option( 'agentorie_chat_settings', array() ); $settings[ $key ] = $value; if ( 'debug_mode' === $key && class_exists( 'Agentorie_Logger' ) ) { Agentorie_Logger::clear_cache(); } // Invalidate static cache in agentorie_chat_get_setting(). agentorie_chat_clear_settings_cache(); return update_option( 'agentorie_chat_settings', $settings ); } /** * Get all settings. * * @since 1.0.0 * @return array All settings. */ function agentorie_chat_get_all_settings() { return get_option( 'agentorie_chat_settings', array() ); } /** * Update multiple settings. * * @since 1.0.0 * @param array $new_settings Settings to update. * @return bool True on success. */ function agentorie_chat_update_settings( $new_settings ) { $settings = get_option( 'agentorie_chat_settings', array() ); $settings = array_merge( $settings, $new_settings ); if ( isset( $new_settings['debug_mode'] ) && class_exists( 'Agentorie_Logger' ) ) { Agentorie_Logger::clear_cache(); } agentorie_chat_clear_settings_cache(); return update_option( 'agentorie_chat_settings', $settings ); } /** * Check if an option is present in synced settings. * * @since 1.0.0 * @param string $option Option key. * @return bool True if option is present. */ function agentorie_chat_has_option( $option ) { if ( 'base_chat' === $option ) { return true; } $options = agentorie_chat_get_setting( 'license_features', array() ); return is_array( $options ) && in_array( $option, $options, true ); } /** * Get human-readable language label from language code. * * Converts BCP-47 language codes (e.g., 'en-US', 'fr-FR', 'cmn-CN') to their * human-readable labels from the supported languages list. * * @since 1.1.2 * @param string $code Language code (e.g., 'en-US', 'fr-FR'). * @return string Human-readable language label, or the original code if not found. */ function agentorie_chat_get_language_label( $code ) { if ( class_exists( 'Agentorie_Admin' ) ) { $labels = Agentorie_Admin::get_supported_languages(); return isset( $labels[ $code ] ) ? $labels[ $code ] : $code; } // Fallback if admin class is not loaded (e.g., frontend-only context). return $code; } /** * Check if license is active. * * @since 1.0.0 * @return bool True if license is active. */ function agentorie_chat_is_license_active() { return 'active' === agentorie_chat_get_setting( 'license_status', 'inactive' ); } /** * Check if plugin is configured. * * @since 1.0.0 * @return bool True if configured. */ function agentorie_chat_is_configured() { return (bool) agentorie_chat_get_setting( 'enabled', false ); } /** * Check if plugin is enabled. * * @since 1.0.0 * @return bool True if enabled. */ function agentorie_chat_is_enabled() { return (bool) agentorie_chat_get_setting( 'enabled', false ); } /** * Check if IP is excluded. * * @since 1.0.0 * @return bool True if IP is excluded. */ function agentorie_chat_is_ip_excluded() { $exclude_ip = agentorie_chat_get_setting( 'exclude_ip', '' ); if ( empty( $exclude_ip ) ) { return false; } return agentorie_chat_get_client_ip() === $exclude_ip; } /** * Get client IP address. * * Wrapper function that delegates to Agentorie_Session::get_client_ip(). * This avoids code duplication while maintaining backward compatibility. * * @since 1.0.0 * @return string Client IP address. */ function agentorie_chat_get_client_ip() { if ( class_exists( 'Agentorie_Session' ) ) { return Agentorie_Session::get_client_ip(); } // Fallback if class not loaded yet (during early initialization). $ip_keys = array( 'HTTP_CF_CONNECTING_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'REMOTE_ADDR' ); foreach ( $ip_keys as $key ) { if ( ! empty( $_SERVER[ $key ] ) ) { $ip = sanitize_text_field( wp_unslash( $_SERVER[ $key ] ) ); if ( false !== strpos( $ip, ',' ) ) { $ips = explode( ',', $ip ); $ip = trim( $ips[0] ); } if ( filter_var( $ip, FILTER_VALIDATE_IP ) ) { return $ip; } } } return ''; } /** * Generate session ID. * * @since 1.0.0 * @return string UUID session ID. */ function agentorie_chat_generate_session_id() { if ( function_exists( 'wp_generate_uuid4' ) ) { return wp_generate_uuid4(); } return sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x', wp_rand( 0, 0xffff ), wp_rand( 0, 0xffff ), wp_rand( 0, 0xffff ), wp_rand( 0, 0x0fff ) | 0x4000, wp_rand( 0, 0x3fff ) | 0x8000, wp_rand( 0, 0xffff ), wp_rand( 0, 0xffff ), wp_rand( 0, 0xffff ) ); } /** * Generate visitor ID. * * @since 1.0.0 * @return string Visitor ID. */ function agentorie_chat_generate_visitor_id() { return substr( md5( uniqid( (string) wp_rand(), true ) ), 0, 12 ); } /** * Sanitize session ID. * * @since 1.0.0 * @param string $session_id Session ID to sanitize. * @return string Sanitized session ID. */ function agentorie_chat_sanitize_session_id( $session_id ) { return preg_replace( '/[^a-zA-Z0-9\-]/', '', $session_id ); } /** * Get assets URL. * * @since 1.0.0 * @param string $path Asset path. * @return string Full asset URL. */ function agentorie_chat_assets_url( $path = '' ) { return AGENTORIE_CHAT_PLUGIN_URL . 'assets/' . ltrim( $path, '/' ); } /** * Get admin URL. * * @since 1.0.0 * @param string $page Admin page slug. * @param array $args Query arguments. * @return string Admin URL. */ function agentorie_chat_admin_url( $page = 'agentorie-chat', $args = array() ) { $args['page'] = $page; return add_query_arg( $args, admin_url( 'admin.php' ) ); } /** * Log a message (deprecated). * * @since 1.0.0 * @deprecated 1.0.0 Use Agentorie_Logger::debug() instead. * @param string $message Log message. * @param array $context Context data. * @return void */ function agentorie_chat_log( $message, $context = array() ) { if ( class_exists( 'Agentorie_Logger' ) ) { Agentorie_Logger::debug( $message, $context ); } } /** * Add plugin action links. * * @since 1.0.0 * @param array $links Existing links. * @return array Modified links. */ function agentorie_chat_plugin_action_links( $links ) { $settings_link = sprintf( '%s', esc_url( admin_url( 'admin.php?page=agentorie-chat-settings' ) ), esc_html__( 'Settings', 'agentorie-chat' ) ); array_unshift( $links, $settings_link ); return $links; } add_filter( 'plugin_action_links_' . AGENTORIE_CHAT_PLUGIN_BASENAME, 'agentorie_chat_plugin_action_links' ); /** * Add plugin row meta. * * @since 1.0.0 * @param array $links Existing meta links. * @param string $file Plugin file. * @return array Modified meta links. */ function agentorie_chat_plugin_row_meta( $links, $file ) { if ( AGENTORIE_CHAT_PLUGIN_BASENAME !== $file ) { return $links; } $row_meta = array( 'docs' => '' . esc_html__( 'Documentation', 'agentorie-chat' ) . '', 'support' => '' . esc_html__( 'Support', 'agentorie-chat' ) . '', ); return array_merge( $links, $row_meta ); } add_filter( 'plugin_row_meta', 'agentorie_chat_plugin_row_meta', 10, 2 ); /** * Cron: Check license. * * @since 1.0.0 * @return void */ function agentorie_chat_cron_check_license() { $license_key = agentorie_chat_get_setting( 'license_key', '' ); if ( ! empty( $license_key ) ) { Agentorie_API::check_license( $license_key, true ); } } add_action( 'agentorie_chat_check_license', 'agentorie_chat_cron_check_license' ); /** * Cron: Cleanup sessions. * * @since 1.0.0 * @return void */ function agentorie_chat_cron_cleanup_sessions() { $retention_hours = absint( agentorie_chat_get_setting( 'conversation_retention_hours', 6 ) ); if ( class_exists( 'Agentorie_Database' ) ) { Agentorie_Database::cleanup_old_sessions( $retention_hours ); } } add_action( 'agentorie_chat_cleanup_sessions', 'agentorie_chat_cron_cleanup_sessions' ); /** * Customize admin footer text on Agentorie pages. * * @since 1.0.0 * @param string $text Default footer text. * @return string Modified footer text. */ function agentorie_chat_admin_footer_text( $text ) { $screen = get_current_screen(); // Only on Agentorie admin pages. if ( $screen && false !== strpos( $screen->id, 'agentorie' ) ) { $text = sprintf( /* translators: %1$s: plugin name, %2$s: five star rating link */ __( 'Enjoying %1$s? We\'d love your %2$s review — it really helps us grow!', 'agentorie-chat' ), 'Agentorie Chat', '★★★★★' ); } return $text; } add_filter( 'admin_footer_text', 'agentorie_chat_admin_footer_text' );