[ 'slides' => [ ... ] ], // existing * '3.1.0' => [ 'slides' => [ ... ] ], // new * Update the constant: * * const CURRENT_INTRO = '3.1.0'; */ const CURRENT_INTRO = '3.0.0'; const META_KEY = 'wpforo_feature_intro_dismissed'; const OPT_OUT_KEY = 'wpforo_feature_intro_opt_out'; const VERSION_OPTION = 'wpforo_feature_intro_version'; private $intros = []; public function __construct() { $this->intros = $this->get_intros(); add_action( 'admin_enqueue_scripts', [ $this, 'maybe_enqueue_assets' ] ); add_action( 'admin_footer', [ $this, 'maybe_render_modal' ] ); } /** * Define all feature introductions. * To add a new intro for a future version, add a new key here. */ private function get_intros() { return [ '3.0.0' => [ 'slides' => [ [ 'title' => __( "What's New in wpForo 3.0 - AI Edition", 'wpforo' ), 'description' => __( 'A major update with a reimagined modern AI-driven discussion system.', 'wpforo' ), 'image' => 'whats-new-page.png', 'features' => [ __( '"What\' New" page. Timeline-based activity feed showing all forum actions: new topics, replies, likes, dislikes and more...', 'wpforo' ), __( 'Refreshed new theme with the brand new Boxed Layout for a clean, card-based forum experience.', 'wpforo' ), __( 'Using Forum as Support Tickets Board. Turns any forum into a private support ticket system.', 'wpforo' ), __( '99.9% Spam Protection with New user control, AI antispam, flood detection and reCAPTCHA v3.', 'wpforo' ), __( 'Block Widgets replacing the legacy widgets.', 'wpforo' ), __( '360° powered AI features shown next →', 'wpforo' ), ], ], [ 'title' => __( 'New Boxed Forum Layout', 'wpforo' ), 'description' => __( 'The fifth forum layout with a modern boxed card design. All five layouts have been refreshed with a clean, contemporary look.', 'wpforo' ), 'image' => 'boxed-forum-layout.png', 'tip' => __( 'Go to wpForo → Forums to change the layout of any forum.', 'wpforo' ), ], [ 'title' => __( '360° AI-Powered Features', 'wpforo' ), 'description' => __( '20+ AI Features are added! wpForo 3.0 introduces revolutionary AI features that make your forum smarter, safer, and more engaging — while keeping you in full control of every feature.', 'wpforo' ), 'image' => 'ai-search-widget.png', 'features' => [ __( 'AI Semantic Search, finds topics by meaning', 'wpforo' ), __( 'AI Translation, 100+ languages, real-time', 'wpforo' ), __( 'AI Topic Summary, instant discussion overviews', 'wpforo' ), __( 'AI Chat Assistant, 24/7 forum chatbot', 'wpforo' ), __( 'AI Content Moderation, spam and toxicity detection.', 'wpforo' ), __( 'AI Topic Suggestions, smart recommendations', 'wpforo' ), ], ], [ 'title' => __( 'Connect to AI Service', 'wpforo' ), 'description' => __( 'Get started with one click — no configuration needed.', 'wpforo' ), 'image' => 'connect-to-service.png', 'steps' => [ __( 'Go to wpForo → AI Features', 'wpforo' ), __( 'Check the Terms of Service checkbox', 'wpforo' ), __( 'Click "Generate API Key & Connect"', 'wpforo' ), ], 'highlight' => __( 'Get started in one click for FREE!', 'wpforo' ), ], [ 'title' => __( 'You\'re in Control', 'wpforo' ), 'description' => __( 'Every AI feature can be independently enabled or disabled. Only spend credits on what you use. Start with just search and add more features anytime.', 'wpforo' ), 'image' => 'ai-settings.png', 'tip' => __( 'Go to wpForo → Settings → AI Features to toggle each feature on or off.', 'wpforo' ), ], ], ], ]; } /** * Should the intro modal be shown to the current user? */ public function should_show() { if( ! current_user_can( 'manage_options' ) ) return false; if( ! $this->is_wpforo_admin_page() ) return false; if( $this->is_user_opted_out() ) return false; if( $this->is_dismissed( self::CURRENT_INTRO ) ) return false; if( ! isset( $this->intros[ self::CURRENT_INTRO ] ) ) return false; return true; } /** * Check if we're on a wpForo admin page. */ private function is_wpforo_admin_page() { if( ! function_exists( 'get_current_screen' ) ) return false; $screen = get_current_screen(); if( ! $screen ) return false; return strpos( $screen->id, 'wpforo' ) !== false; } /** * Check if user permanently opted out of all intros. */ private function is_user_opted_out() { return (bool) get_user_meta( get_current_user_id(), self::OPT_OUT_KEY, true ); } /** * Check if user dismissed a specific intro version. */ private function is_dismissed( $version ) { $dismissed = get_user_meta( get_current_user_id(), self::META_KEY, true ); if( ! is_array( $dismissed ) ) $dismissed = []; return isset( $dismissed[ $version ] ); } /** * Enqueue CSS and JS for the intro modal. */ public function maybe_enqueue_assets() { if( ! $this->should_show() ) return; wp_enqueue_style( 'wpforo-feature-intro', WPFORO_URL . '/admin/assets/css/feature-intro.css', [], WPFORO_VERSION ); wp_enqueue_script( 'wpforo-feature-intro', WPFORO_URL . '/admin/assets/js/feature-intro.js', [ 'jquery' ], WPFORO_VERSION, true ); wp_localize_script( 'wpforo-feature-intro', 'wpforoFeatureIntro', [ 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'nonce' => wp_create_nonce( 'wpforo_feature_intro' ), 'version' => self::CURRENT_INTRO, 'connectUrl' => admin_url( 'admin.php?page=wpforo-ai' ), 'totalSlides' => count( $this->intros[ self::CURRENT_INTRO ]['slides'] ), ] ); } /** * Render the modal HTML in admin footer. */ public function maybe_render_modal() { if( ! $this->should_show() ) return; $intro = $this->intros[ self::CURRENT_INTRO ]; $slides = $intro['slides']; $img_url = WPFORO_URL . '/assets/images/intro/'; ?>