dispatch_hooks(); } public static function dispatch_hooks() { $self = new self(); add_action( 'init', array( $self, 'dispatch_notices' ) ); add_action( 'admin_init', array( $self, 'handle_admin_request' ) ); } public static function dispatch_notices() { if ( self::has_upgrade_to_pro_notice() ) { self::add_notice('pro_upgrade_discount_offer', [ 'type' => 'discount_offer', 'coupon_code' => 'i9hE7i', 'message' => wp_kses_post( __( 'Up to 40% Off', 'academy' ) ), 'button_text' => __( 'Claim Discount', 'academy' ), 'button_action' => 'https://academylms.net/pricing/', 'dismissible' => true ]); } if ( current_user_can( 'manage_options' ) && ! get_option( 'users_can_register' ) ) { self::add_notice('users_can_register', [ 'type' => 'info', 'message' => wp_kses_post( __( 'Membership option is turned off, students and instructors will not be able to sign up. Press Enable or go to Settings > General > Membership and enable "Anyone can register".', 'academy' ) ), 'button_text' => __( 'Enable Registration', 'academy' ), 'button_action' => esc_url(add_query_arg(array( 'academy-registration' => 'enable', 'security' => wp_create_nonce( 'academy_nonce' ), ))) ]); } // Academy Certificate Related notice if ( Helper::get_addon_active_status( 'certificates' ) && Helper::is_plugin_active( 'academy-certificates/academy-certificates.php' ) ) { self::add_notice('deprecated_certificate_addon', [ 'type' => 'danger', 'message' => esc_html__( 'To avoid conflicts, please first deactivate the Academy Certificate plugin.', 'academy' ), 'button_text' => __( 'Deactivate Academy Certificate Plugin.', 'academy' ), 'button_action' => esc_url(add_query_arg(array( 'deactivate-academy-certificates' => true, 'security' => wp_create_nonce( 'academy_nonce' ), ))) ]); } elseif ( Helper::is_plugin_active( 'academy-certificates/academy-certificates.php' ) ) { self::add_notice('deprecated_certificate_addon', [ 'type' => 'danger', 'message' => esc_html__( "We've detected that you're using a deprecated certificate addon. We've rebuilt the certificate feature, which is now included in Academy LMS—no need to install an extra plugin.", 'academy' ), 'button_text' => __( 'Learn More', 'academy' ), 'button_action' => esc_url(add_query_arg(array( 'deactivate-academy-certificates' => true, 'security' => wp_create_nonce( 'academy_nonce' ), ))) ]); }//end if // Check Academy Pro Minimum version notice if ( Helper::is_active_academy_pro() && ! version_compare( implode( '.', array_slice( explode( '.', ACADEMY_VERSION ), 0, 2 ) ), implode( '.', array_slice( explode( '.', ACADEMY_PRO_REQUIRED_CORE_VERSION ), 0, 2 ) ), '=' ) ) { self::add_notice('version_conflicts_academy_pro', [ 'type' => 'danger', 'message' => esc_html__( 'You are using an outdated version of Academy LMS Pro. Please update to the latest version to ensure compatibility and prevent conflicts.', 'academy' ), 'button_text' => esc_html__( 'Update Now', 'academy' ), 'button_action' => esc_url( admin_url( 'options-general.php?page=academy-pro' ) ), ]); } self::maybe_add_gemsecurity_notice(); self::maybe_add_recaptcha_deprecation_notice(); } /** * Cascading GemSecurity compatibility notice: install → activate → enable * Login Security. At most one of the three fires per request, evaluated * in severity order, so admins never see stacked/contradictory warnings. * * Only surfaces for sites that actually use Academy's deprecated Social * Login or reCAPTCHA features — GemSecurity is their unified replacement * (Social Login + reCAPTCHA + brute-force protection for the * `academy/v1/login` endpoint), so the recommendation is irrelevant, and * the notice stays hidden, on installs that use neither. */ public static function maybe_add_gemsecurity_notice() { if ( ! current_user_can( 'manage_options' ) ) { return; } // Gate: only relevant when Social Login or reCAPTCHA is in use. $uses_social_login = Helper::get_addon_active_status( 'social-login', true ); $uses_recaptcha = (bool) Helper::get_settings( 'is_enabled_recaptcha', false ); if ( ! $uses_social_login && ! $uses_recaptcha ) { return; } if ( ! Helper::is_plugin_installed( 'gemsecurity/gemsecurity.php' ) ) { self::add_notice( 'academy_gemsecurity_status', [ 'type' => 'warning', 'message' => esc_html__( 'You\'re using Academy\'s Social Login and/or reCAPTCHA. GemSecurity is their all-in-one replacement — it handles Social Login, reCAPTCHA, and adds brute-force protection to your login. Download and install it to get started.', 'academy' ), 'button_text' => __( 'Download GemSecurity', 'academy' ), 'button_action' => 'https://store.kodezen.com/se-download/free/gemsecurity/latest/', 'dismissible' => true, ] ); return; } if ( ! Helper::is_active_gemsecurity() ) { self::add_notice( 'academy_gemsecurity_status', [ 'type' => 'warning', 'message' => esc_html__( 'GemSecurity is installed but not active. Activate it to replace Academy\'s Social Login and reCAPTCHA with its own modules and protect your login against brute-force attacks.', 'academy' ), 'button_text' => __( 'Activate GemSecurity', 'academy' ), 'button_action' => esc_url( add_query_arg( array( 'academy-gemsecurity-activate' => true, 'security' => wp_create_nonce( 'academy_nonce' ), ) ) ), 'dismissible' => true, ] ); return; } // GemSecurity is active — check whether Login Security (the one // module Academy's integration actually relies on) is on. Guarded // with has_filter() and a `true` default so an older GemSecurity // version without this filter, or an unrecognized module id, never // falsely triggers the notice (fail silent, not fail nagging). if ( has_filter( 'gemsecurity/module_active' ) && ! apply_filters( 'gemsecurity/module_active', true, 'login-security' ) ) { self::add_notice( 'academy_gemsecurity_status', [ 'type' => 'warning', 'message' => esc_html__( 'GemSecurity\'s Login Security module is disabled. Enable it to protect Academy\'s login against brute-force attacks.', 'academy' ), 'button_text' => __( 'Enable Protection', 'academy' ), 'button_action' => esc_url( admin_url( 'admin.php?page=gemsecurity-modules' ) ), 'dismissible' => true, ] ); } } /** * The reCAPTCHA option is deprecated ahead of GemSecurity's own replacement, which * does not exist yet for Academy — so this notice never says "switch * now" or escalates; the message is constant until v4.1.0 actually ships * a working alternative. */ public static function maybe_add_recaptcha_deprecation_notice() { if ( ! \Academy\Helper::get_settings( 'is_enabled_recaptcha', false ) ) { return; } self::add_notice( 'deprecated_recaptcha_feature', [ 'type' => 'warning', 'message' => esc_html__( 'Academy\'s built-in reCAPTCHA is deprecated and will be replaced by GemSecurity\'s own reCAPTCHA integration in v4.1.0.', 'academy' ), 'button_text' => __( 'Learn More', 'academy' ), 'button_action' => 'https://academylms.net/docs/how-to-use-google-recaptcha-with-academy-lms/', 'dismissible' => true, ] ); } public static function add_notice( $notice_name, $args ) { $defaults = array( 'type' => 'info', 'message' => '', 'button_text' => 'Click Here', 'button_action' => '#', 'dismissible' => false, 'notice_key' => $notice_name, ); $args = wp_parse_args( $args, $defaults ); // A dismissible notice the current user already closed (see // Ajax\Miscellaneous::dismiss_admin_notice()) never gets added in the // first place — filtered here, once, for every call site, so it can't // flash on render and any future notice added through this same // method automatically gets persistent per-user dismissal for free. // `pro_upgrade_discount_offer` keeps its own pre-existing site-wide // dismissal (gated earlier in has_upgrade_to_pro_notice()), untouched. if ( ! empty( $args['dismissible'] ) && in_array( $notice_name, self::get_dismissed_notices(), true ) ) { return; } self::$notices[ $notice_name ] = $args; } /** * Notice keys the current user has dismissed (per-user, via user meta — * see Ajax\Miscellaneous::dismiss_admin_notice()). * * @return string[] */ private static function get_dismissed_notices() { $user_id = get_current_user_id(); if ( ! $user_id ) { return array(); } $dismissed = get_user_meta( $user_id, 'academy_dismissed_admin_notices', true ); return is_array( $dismissed ) ? $dismissed : array(); } public static function get_notices() { return self::$notices; } public static function has_upgrade_to_pro_notice() { if ( \Academy\Helper::is_plugin_installed( 'academy-pro/academy-pro.php' ) || get_option( 'academy_pro_version' ) || get_option( 'academy_disabled_pro_upgrade_discount_offer' ) ) { return false; } $saved_time = get_option( 'academy_first_install_time' ); $three_days_ago = Helper::get_time() - ( 3 * DAY_IN_SECONDS ); if ( $saved_time < $three_days_ago ) { return true; } return false; } public function handle_admin_request() { if ( isset( $_GET['security'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['security'] ) ), 'academy_nonce' ) ) { if ( isset( $_GET['academy-registration'] ) && 'enable' === $_GET['academy-registration'] ) { $this->enabled_registration_notice(); } elseif ( isset( $_GET['academy-dismiss-notice'] ) && 'pro_upgrade_discount_offer' === $_GET['academy-dismiss-notice'] ) { $this->pro_upgrade_discount_offer(); } elseif ( isset( $_GET['deactivate-academy-certificates'] ) && (bool) $_GET['deactivate-academy-certificates'] ) { $this->deactivated_certificate_addon(); } elseif ( isset( $_GET['academy-gemsecurity-activate'] ) && (bool) $_GET['academy-gemsecurity-activate'] ) { $this->activate_gemsecurity(); } } } public function enabled_registration_notice() { if ( current_user_can( 'manage_options' ) ) { update_option( 'users_can_register', true ); wp_safe_redirect( admin_url( 'admin.php?page=academy' ) ); exit; } } public function pro_upgrade_discount_offer() { if ( current_user_can( 'manage_academy_instructor' ) ) { add_option( 'academy_disabled_pro_upgrade_discount_offer', true, '', 'no' ); wp_safe_redirect( admin_url( 'admin.php?page=academy' ) ); exit; } } public function deactivated_certificate_addon() { if ( current_user_can( 'activate_plugins' ) ) { deactivate_plugins( 'academy-certificates/academy-certificates.php' ); wp_safe_redirect( admin_url( 'admin.php?page=academy' ) ); exit; } } public function activate_gemsecurity() { if ( current_user_can( 'activate_plugins' ) ) { if ( ! function_exists( 'activate_plugin' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } activate_plugin( 'gemsecurity/gemsecurity.php' ); wp_safe_redirect( admin_url( 'admin.php?page=academy' ) ); exit; } } }