array( 'copied' => __( 'Copied!', 'abilities-bridge' ), 'copyFailed' => __( 'Failed to copy to clipboard', 'abilities-bridge' ), ), ) ); } /** * Verify the page navigation nonce. * * Returns true if nonce is valid or not present (first page load). * Dies with error if nonce is present but invalid. * * @return bool True if nonce is valid, false if no nonce present. */ private static function verify_page_nonce() { $nonce = filter_input( INPUT_GET, '_wpnonce', FILTER_SANITIZE_FULL_SPECIAL_CHARS ); // No nonce present = first page load or redirect, allow with defaults. if ( empty( $nonce ) ) { return false; } // Nonce present - verify it. if ( ! wp_verify_nonce( $nonce, 'abilities_bridge_welcome_nav' ) ) { wp_die( esc_html__( 'Security verification failed. Please use the navigation links provided.', 'abilities-bridge' ), esc_html__( 'Security Check Failed', 'abilities-bridge' ), array( 'response' => 403 ) ); } return true; } /** * Get the current admin page parameter for redirect checks. * * This is used internally for redirect logic only - it does not require * nonce verification because it only checks the current page name * (a WordPress-controlled parameter) for routing purposes. * * @return string The current page parameter value, or empty string. */ private static function get_current_page() { $value = filter_input( INPUT_GET, 'page', FILTER_SANITIZE_FULL_SPECIAL_CHARS ); return ! empty( $value ) ? sanitize_key( $value ) : ''; } /** * Get a sanitized GET parameter for display/routing purposes. * * Verifies nonce before accessing parameters. If no nonce is present * (first page load), returns the default value. * * @param string $key Parameter key. * @param string $default Default value. * @return string Sanitized value. */ private static function get_url_param( $key, $default = '' ) { // Verify nonce - returns false if no nonce (first load). if ( ! self::verify_page_nonce() ) { return $default; } $value = filter_input( INPUT_GET, $key, FILTER_SANITIZE_FULL_SPECIAL_CHARS ); return ! empty( $value ) ? sanitize_key( $value ) : $default; } /** * Check for activation redirect transient */ public static function check_activation_redirect() { // Skip if setup already completed. if ( self::is_setup_complete() ) { // Delete transient only after setup is complete. if ( get_transient( 'abilities_bridge_activation_redirect' ) !== false ) { delete_transient( 'abilities_bridge_activation_redirect' ); } return; } // Skip if doing AJAX or cron. if ( wp_doing_ajax() || wp_doing_cron() ) { return; } // Skip if already on wizard page - use get_current_page() for redirect checks // because current screen is not yet initialized on admin_init for hidden admin pages. $page = self::get_current_page(); if ( 'abilities-bridge-welcome' === $page ) { return; } // Check for first-time activation redirect (transient exists). $activation_redirect = get_transient( 'abilities_bridge_activation_redirect' ); if ( false !== $activation_redirect ) { // Delete transient (redirect only once on first admin page load). delete_transient( 'abilities_bridge_activation_redirect' ); // Redirect to welcome wizard on first load after activation. wp_safe_redirect( admin_url( 'admin.php?page=abilities-bridge-welcome' ) ); exit; } // Only redirect if trying to access Abilities Bridge pages. // This prevents hijacking the entire WordPress admin. $is_ai_admin_page = strpos( $page, 'abilities-bridge' ) === 0; if ( ! $is_ai_admin_page ) { // Not on an Abilities Bridge page, allow access. return; } // User is trying to access Abilities Bridge pages without completing setup. // Redirect to welcome wizard. wp_safe_redirect( admin_url( 'admin.php?page=abilities-bridge-welcome' ) ); exit; } /** * Check if plugin version requires re-consent */ public static function check_consent_version() { // Skip if not setup yet. if ( ! self::is_setup_complete() ) { return; } $current_version = ABILITIES_BRIDGE_VERSION; $consent_version = get_option( 'abilities_bridge_consent_version', '0' ); // Define versions that require re-consent (major updates). $versions_requiring_reconsent = array( '2.0.0', '3.0.0' ); // Check if current version needs re-consent. if ( in_array( $current_version, $versions_requiring_reconsent, true ) && version_compare( $consent_version, $current_version, '<' ) ) { // Disable functionality until re-consented. update_option( 'abilities_bridge_functionality_disabled', true ); // Show admin notice. add_action( 'admin_notices', array( __CLASS__, 'show_reconsent_notice' ) ); } } /** * Show re-consent notice */ public static function show_reconsent_notice() { $reconsent_url = wp_nonce_url( admin_url( 'admin.php?page=abilities-bridge-welcome&reconsent=1' ), 'abilities_bridge_welcome_nav' ); ?>
array( 'new' => array( 'Connected Plugins now use a documented integration contract', 'Integration cards can show disabled and no-abilities states with provider settings links', 'Approval profiles can approve explicit ability lists', ), 'changes' => array( 'Removed hardcoded provider detection in favor of provider-registered integrations', 'Integration discovery now safely skips malformed provider data', ), 'security' => array( 'Integration metadata is validated before rendering or approval', 'Provider settings links are restricted to local admin pages', ), ), '2.0.0' => array( 'new' => array( 'New built-in tool: execute_wp_cli - Execute WP-CLI commands (read-only)', 'New ability: ability_core/update-plugin - Update WordPress plugins', 'Enhanced logging: IP addresses now included in activity logs', ), 'changes' => array( 'Memory tool now supports up to 100MB total storage (was 50MB)', 'Database queries now limited to 30 second timeout', 'MCP integration now requires OAuth 2.0 authentication', ), 'security' => array( 'Added rate limiting: 100 requests per hour default', 'New permission gate: requires_two_factor_auth for sensitive abilities', ), ), ); if ( ! isset( $changelogs[ $version ] ) ) { return ''; } $changelog = $changelogs[ $version ]; $html = ''; if ( ! empty( $changelog['new'] ) ) { $html .= '