# bbp-core/trunk/includes/functions.php

Forumax – AI Powered Advanced Community Forum Plugin, version trunk. 972 lines.

- Page: https://pluginprobe.com/plugins/bbp-core/trunk/code/includes/functions.php
- Raw: https://pluginprobe.com/plugins/bbp-core/trunk/raw/includes/functions.php
- Modified: 2026-08-13T11:39:12+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/bbp-core/trunk/code/includes/functions.php#L10-L20`.

```php
<?php
/**
 * Get the value of a settings field.
 *
 * @param string $option  settings field name
 * @param string $section the section name this field belongs to
 * @param string $default default text if it's not found
 *
 * @return mixed
 */
function forumax_get_opt( $option, $default = '' ) {
	$options = get_option( 'bbp_core_settings' );

	if ( isset( $options[ $option ] ) ) {
		return $options[ $option ];
	}

	return $default;
}

/**
 * Render the guest login form used on topic/reply/forum prompts.
 *
 * Uses the Shortcodes setting when set; otherwise falls back to [forumax_login_form]
 * so Sign In / Create Account tabs always appear (when registration is allowed).
 *
 * @return void
 */
function forumax_render_topic_login_form() {
	$shortcode = '';

	if ( function_exists( 'forumax_get_opt' ) ) {
		$shortcode = trim( (string) forumax_get_opt( 'topic_login_shortcode', '[forumax_login_form]' ) );
	}

	if ( '' === $shortcode ) {
		$shortcode = '[forumax_login_form]';
	}

	echo do_shortcode( $shortcode ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}

/**
 * Check if a plugin has been installed for specific number of days
 *
 * @param string $plugin_path The plugin path (e.g. 'woocommerce/woocommerce.php')
 * @param int    $days        Number of days to check against
 * @return bool  True if plugin is installed for specified days, false otherwise
 */
function forumax_is_plugin_installed_for_days( $days, $plugin_slug = 'bbpc' ) {
	// Get the installation timestamp of the plugin
	$installed_time = get_option( $plugin_slug . '_installed' );

	// Ensure it's a valid timestamp
	if ( ! is_numeric( $installed_time ) || $installed_time <= 0 ) {
		return false;
	}

	// Convert days to seconds
	$required_time = (int) $days * DAY_IN_SECONDS;

	// Get the current UTC time
	$current_time = time();

	// Check if the plugin has been installed for the required duration
	return ( $current_time - $installed_time ) >= $required_time;
}

/**
 * Check If the Page is Forum page
 */
function forumax_is_forum_page() {
	if ( in_array( 'bbpress', get_body_class(), true ) ) {
		return true;
	}
	return false;
}

/**
 * Add a stable body class for all Forumax/bbPress related pages.
 *
 * This helps themes (e.g. Hello Biz) and custom CSS target Forumax pages
 * reliably, even when the page uses shortcodes/blocks instead of bbPress
 * native endpoints.
 *
 * @param array $classes Existing body classes.
 * @return array
 */
function forumax_add_forumax_body_class( $classes ) {
	if ( is_admin() ) {
		return $classes;
	}

	$should_add = false;

	// Native bbPress pages (forums, topics, replies, search, user profiles, etc.).
	if ( function_exists( 'is_bbpress' ) && is_bbpress() ) {
		$should_add = true;
	}

	// Some installs load forum content via shortcodes/blocks on normal pages.
	if ( ! $should_add ) {
		$post = get_post();
		if ( $post instanceof WP_Post ) {
			$shortcodes = array(
				// bbPress shortcodes.
				'bbp-forum-index',
				'bbp-forum-form',
				'bbp-single-forum',
				'bbp-topic-index',
				'bbp-topic-form',
				'bbp-single-topic',
				'bbp-reply-form',
				'bbp-single-reply',
				'bbp-single-view',
				'bbp-search-form',
				'bbp-search',
				'bbp-login',
				'bbp-register',
				'bbp-lost-pass',

				// Forumax shortcodes.
				'forumax_login_form',
				'forumax_chat',
			);

			foreach ( $shortcodes as $shortcode ) {
				if ( has_shortcode( $post->post_content, $shortcode ) ) {
					$should_add = true;
					break;
				}
			}

			// Also check for bbPress/Forumax blocks.
			if ( ! $should_add && function_exists( 'has_block' ) ) {
				$blocks = array(
					'bbpress/forum-index',
					'bbpress/topic-index',
					'forumax/forums',
				);

				foreach ( $blocks as $block ) {
					if ( has_block( $block, $post ) ) {
						$should_add = true;
						break;
					}
				}
			}
		}
	}

	if ( $should_add && ! in_array( 'forumax-body', $classes, true ) ) {
		$classes[] = 'forumax-body';
	}

	return $classes;
}
add_filter( 'body_class', 'forumax_add_forumax_body_class', 20 );

/**
 * Get hashed IP address for anonymous user identification.
 *
 * This function creates a one-way hash of the user's IP address,
 * making it impossible to recover the original IP while still
 * allowing duplicate detection (same IP = same hash).
 *
 * GDPR Compliant: Real IP addresses are never stored in the database.
 *
 * @since 2.3.1
 *
 * @return string Hashed IP address (64 characters)
 */
function forumax_get_hashed_ip() {
	$ip = '';

	// Get the real IP address (handles proxies and load balancers)
	if ( ! empty( $_SERVER['HTTP_CLIENT_IP'] ) ) {
		$ip = $_SERVER['HTTP_CLIENT_IP'];
	} elseif ( ! empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
		// Can contain multiple IPs, get the first one
		$ip_list = explode( ',', $_SERVER['HTTP_X_FORWARDED_FOR'] );
		$ip      = trim( $ip_list[0] );
	} elseif ( ! empty( $_SERVER['REMOTE_ADDR'] ) ) {
		$ip = $_SERVER['REMOTE_ADDR'];
	}

	// Sanitize the IP
	$ip = filter_var( $ip, FILTER_VALIDATE_IP ) ? $ip : 'unknown';

	// Use WordPress salt for extra security (unique per site)
	$salt = defined( 'NONCE_SALT' ) ? NONCE_SALT : 'forumax_default_salt_key';

	// Create one-way hash - cannot be reversed to get original IP
	return hash( 'sha256', $ip . $salt );
}

/**
 * Get raw IP address for backward compatibility check only.
 *
 * WARNING: This function is used ONLY to check for legacy vote entries.
 * The raw IP is NEVER stored - it's immediately compared against legacy
 * data and then discarded. New votes always use forumax_get_hashed_ip().
 *
 * @since 2.3.1
 * @access private
 *
 * @return string|false Raw IP address or false if invalid
 */
function forumax_get_raw_ip() {
	$ip = '';

	if ( ! empty( $_SERVER['HTTP_CLIENT_IP'] ) ) {
		$ip = $_SERVER['HTTP_CLIENT_IP'];
	} elseif ( ! empty( $_SERVER['HTTP_X_FORWARDED_FOR'] ) ) {
		$ip_list = explode( ',', $_SERVER['HTTP_X_FORWARDED_FOR'] );
		$ip      = trim( $ip_list[0] );
	} elseif ( ! empty( $_SERVER['REMOTE_ADDR'] ) ) {
		$ip = $_SERVER['REMOTE_ADDR'];
	}

	return filter_var( $ip, FILTER_VALIDATE_IP ) ? $ip : false;
}

/**
 * Turn on the WordPress visual editor for bbPress
 *
 * @param array $args
 * @return array
 */
function forumax_bbp_enable_visual_editor( $args = [] ) {
	$args['tinymce']        = true;
	$args['default_editor'] = 'tinymce';
	return $args;
}
add_filter( 'bbp_after_get_the_content_parse_args', 'forumax_bbp_enable_visual_editor' );

/**
 * Sanitize topic and reply content to prevent raw HTML display.
 *
 * When topics/replies are submitted from the frontend, bbPress runs
 * `bbp_encode_bad` which entity-encodes HTML tags (e.g. `<p>` becomes
 * `&lt;p&gt;`). This causes HTML tags to display as visible plain text
 * instead of being rendered by the browser.
 *
 * This filter detects entity-encoded HTML, decodes it back to real HTML,
 * and sanitizes the output through `wp_kses_post()` to allow only safe
 * post-level HTML tags (p, strong, em, a, br, etc.) while stripping
 * dangerous elements like script or iframe.
 *
 * @since 2.2.2
 *
 * @param string $content The topic or reply content.
 * @return string Sanitized content with proper HTML rendering.
 */
function forumax_sanitize_block_content( $content ) {
	// Bail early if content is empty.
	if ( empty( $content ) ) {
		return $content;
	}

	// Check if content contains entity-encoded HTML tags (e.g. &lt;p&gt;).
	if ( strpos( $content, '&lt;' ) !== false ) {
		// Decode entity-encoded HTML back to real HTML tags.
		$content = html_entity_decode( $content, ENT_QUOTES, 'UTF-8' );

		// Sanitize with wp_kses_post — allows safe HTML (p, strong, a, br, etc.)
		// but strips dangerous tags (script, iframe, etc.).
		$content = wp_kses_post( $content );
	}

	// Strip inline style attributes from all HTML tags.
	$content = preg_replace( '/\s+style="[^"]*"/i', '', $content );

	// Strip block-editor class attributes (e.g. wp-block-paragraph).
	$content = preg_replace( '/\s+class="wp-block-[^"]*"/i', '', $content );

	// Clean up any remaining empty class attributes.
	$content = preg_replace( '/\s+class=""/i', '', $content );

	return $content;
}
add_filter( 'bbp_get_topic_content', 'forumax_sanitize_block_content', 4 );
add_filter( 'bbp_get_reply_content', 'forumax_sanitize_block_content', 4 );

/**
 * Check if the pro plugin and plan is active
 *
 * @return bool|void
 */
function forumax_is_premium() {
	if ( class_exists('Forumax_Pro') && bc_fs()->can_use_premium_code() ) {
		return true;
	}
}

/**
 * Check if the promax plan is active
 *
 * @return bool|void
 */
function forumax_is_promax() {
	if ( class_exists('Forumax_Pro') && bc_fs()->can_use_premium_code() && bc_fs()->is_plan('promax') ) {
		return true;
	}
}

/**
 * Forumax Admin pages
 *
 * Checks if the current admin page matches the specified Forumax page type.
 *
 * @param string $admin The admin page type to check ('admin', 'settings', 'dashboard').
 *
 * @return bool True if on the specified admin page, false otherwise.
 */
function forumax_admin_pages( $admin ) {
	$current_url = ! empty( $_GET['page'] ) ? admin_url( 'admin.php?page=' ) . sanitize_text_field( wp_unslash( $_GET['page'] ) ) : '';

	if ( 'admin' === $admin ) {
		if ( admin_url( 'admin.php?page=forumax-builder' ) === $current_url ) {
			return true;
		}
	} elseif ( 'settings' === $admin ) {
		if ( admin_url( 'admin.php?page=forumax-settings' ) === $current_url ) {
			return true;
		}
	} elseif ( 'dashboard' === $admin ) {
		if ( admin_url( 'admin.php?page=forumax' ) === $current_url ) {
			return true;
		}
	} elseif ( 'setup' === $admin ) {
		if ( admin_url( 'admin.php?page=forumax-setup' ) === $current_url ) {
			return true;
		}
	} elseif ( 'analytics' === $admin ) {
		if ( admin_url( 'admin.php?page=forumax-analytics' ) === $current_url ) {
			return true;
		}
	}

	return false;
}


/**
 * BBP Forum Assets
 * Checks if the current page is a single forum or a single topic.
 *
 * @return bool True if the current page is a single forum or topic, false otherwise.
 */
function forumax_forum_and_topic_page(){
	if ( bbp_is_single_forum() ||  bbp_is_single_topic() || bbp_is_reply_edit()) {
		return true;
	}
}


/**
 * Posts Arraty
 * @param object Post Type
 */
function forumax_get_posts( $post_type = 'forum' ) {
	$posts = get_pages(
		[
			'post_type' => $post_type,
			'parent'    => 0,
		]
	);

	$posts_array = [];

	if ( $posts ) {
		foreach ( $posts as $post ) {
			$posts_array[ $post->ID ] = $post->post_title;
		}
	}

	return $posts_array;
}

/**
 * Limit letter
 * @param $string
 * @param $limit_length
 * @param string $suffix
 */
function forumax_limit_letter( $string, $limit_length, $suffix = '...' ) {
	if ( strlen( $string ) > $limit_length ) {
		echo esc_html ( strip_shortcodes( substr( $string, 0, $limit_length ) . $suffix ) );
	} else {
		echo esc_html( $string );
	}
}

/**
 * Return the topic view count.
 *
 * @param int $topic_id Optional. Topic id
 *
 * @return int The view count
 * @uses get_post_meta() To get the view count meta
 * @uses bbp_get_topic_id() To get the topic id
 */
function forumax_get_topic_view_count( $topic_id = 0 ) {
	$topic_id = bbp_get_topic_id( $topic_id );

	if ( empty( $topic_id ) ) {
		return 0;
	}

	$views = (int) get_post_meta( $topic_id, '_btv_view_count', true );

	return $views;
}

/**
 * Output the topic view count.
 *
 * @param int $topic_id Optional. Topic id
 *
 * @uses bbp_get_topic_id() To get the topic id
 * @uses btv_get_topic_view_count() To get the view count for the topic
 */
function forumax_topic_view_count( $topic_id = 0 ) {
	$topic_id   = bbp_get_topic_id( $topic_id );
	$view_count = forumax_get_topic_view_count( $topic_id );
	return $view_count;
}

/**
 * Increment the topic view count.
 *
 * Increments the view count when a visitor views a topic.
 * Uses cookies to prevent duplicate counts within a session (1 hour).
 *
 * @param int $topic_id Topic ID.
 * @return bool True if view was counted, false otherwise.
 */
function forumax_increment_topic_view_count( $topic_id = 0 ) {
	$topic_id = bbp_get_topic_id( $topic_id );

	if ( empty( $topic_id ) ) {
		return false;
	}

	// Optionally: Don't count views for logged-in admins/moderators
	// Uncomment the following to exclude admin views from being counted
	// if ( current_user_can( 'moderate' ) ) {
	// 	return false;
	// }

	// Use a cookie to prevent duplicate counts within the same session
	$cookie_name = 'forumax_viewed_' . $topic_id;

	// Check if this topic was already viewed in this session
	if ( isset( $_COOKIE[ $cookie_name ] ) ) {
		return false;
	}

	// Get current view count
	$current_views = (int) get_post_meta( $topic_id, '_btv_view_count', true );

	// Increment the view count
	$new_views = $current_views + 1;

	// Update the view count
	update_post_meta( $topic_id, '_btv_view_count', $new_views );

	// Set cookie to prevent duplicate counting (expires in 1 hour)
	setcookie( $cookie_name, '1', time() + HOUR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN, is_ssl(), true );

	return true;
}

/**
 * Track topic views on template redirect.
 *
 * Hooks into bbPress template redirect to track topic views
 * when a single topic page is loaded.
 */
function forumax_track_topic_views() {
	// Only track on single topic pages
	if ( ! function_exists( 'bbp_is_single_topic' ) || ! bbp_is_single_topic() ) {
		return;
	}

	// Get the current topic ID
	$topic_id = bbp_get_topic_id();

	if ( ! empty( $topic_id ) ) {
		forumax_increment_topic_view_count( $topic_id );
	}
}
add_action( 'bbp_template_redirect', 'forumax_track_topic_views', 20 );

/**
 * Get forum title
 * @return string
 */
function forumax_forum_title(){
    $forum_id       = bbp_get_forum_id();
    $forum_title    = get_the_title( $forum_id );
    return $forum_title;
}

/**
 * Customizer section hide from customizer
 */
add_action( 'customize_register', function( $wp_customize ) {
    // Unset the section you want to hide
    $wp_customize->remove_section( 'design_fields' );
}, 20 );

/**
 * Get all the registered menus
 */
function forumax_get_registered_nav_menus() {
	$menus          = get_registered_nav_menus();
	$menu_locations = [];
	$empty          = [ '' => esc_html__('Select Menu Location', 'forumax') ];
	foreach ( $menus as $location => $description ) {
		$menu_locations[ $location ] = $description;
	}

	return $empty + $menu_locations;
}

/**
 * Fix Gutenberg editor support for bbPress forum and topic post types.
 *
 * Filters the post type registration arguments to enable full REST API
 * support, ensure thumbnail support, and force correct labels so that
 * Gutenberg displays the proper post type name instead of "Document".
 *
 * @param array  $args      Array of arguments for registering a post type.
 * @param string $post_type Post type key.
 * @return array Modified arguments.
 */
function forumax_fix_post_type_args( $args, $post_type ) {
	$post_types_config = array(
		'forum' => array(
			'rest_base' => 'forums',
			'labels'    => array(
				'name'          => __( 'Forums', 'bbpress' ),
				'singular_name' => __( 'Forum', 'bbpress' ),
			),
		),
		'topic' => array(
			'rest_base' => 'topics',
			'labels'    => array(
				'name'          => __( 'Topics', 'bbpress' ),
				'singular_name' => __( 'Topic', 'bbpress' ),
			),
		),
	);

	if ( ! isset( $post_types_config[ $post_type ] ) ) {
		return $args;
	}

	$config = $post_types_config[ $post_type ];

	// Force full REST API support for Gutenberg.
	$args['show_in_rest']          = true;
	$args['rest_base']             = $config['rest_base'];
	$args['rest_controller_class'] = 'WP_REST_Posts_Controller';

	// Set explicit label property (singular string).
	$args['label'] = $config['labels']['name'];

	// Ensure thumbnail support is included.
	if ( ! empty( $args['supports'] ) && is_array( $args['supports'] ) ) {
		if ( ! in_array( 'thumbnail', $args['supports'], true ) ) {
			$args['supports'][] = 'thumbnail';
		}
	} else {
		$args['supports'] = array( 'title', 'editor', 'revisions', 'thumbnail' );
	}

	// Merge labels to prevent Gutenberg "Document" fallback.
	if ( ! empty( $args['labels'] ) && is_array( $args['labels'] ) ) {
		$args['labels'] = array_merge( $args['labels'], $config['labels'] );
	} else {
		$args['labels'] = $config['labels'];
	}

	return $args;
}
add_filter( 'register_post_type_args', 'forumax_fix_post_type_args', 99, 2 );

/**
 * Grant bbPress post type capabilities to WordPress administrators.
 *
 * Gutenberg requests the post type REST endpoint with context=edit, which
 * checks custom capabilities like edit_forums and edit_topics. WordPress
 * administrators may not have these caps unless bbPress has explicitly
 * assigned them. This filter dynamically grants the required caps to
 * any user who can manage_options (i.e. administrators).
 *
 * @param array $allcaps All capabilities for the user.
 * @param array $caps    Required capabilities being checked.
 * @param array $args    Additional arguments passed to the check.
 * @return array Modified capabilities array.
 */
function forumax_grant_bbpress_caps_to_admins( $allcaps, $caps, $args ) {
	// Only grant to users who can manage options (administrators).
	if ( empty( $allcaps['manage_options'] ) ) {
		return $allcaps;
	}

	// bbPress forum and topic capabilities needed for REST API access.
	$bbpress_caps = array(
		'edit_forums',
		'edit_others_forums',
		'publish_forums',
		'read_private_forums',
		'read_hidden_forums',
		'delete_forums',
		'delete_others_forums',
		'edit_topics',
		'edit_others_topics',
		'publish_topics',
		'read_private_topics',
		'delete_topics',
		'delete_others_topics',
	);

	foreach ( $bbpress_caps as $cap ) {
		$allcaps[ $cap ] = true;
	}

	return $allcaps;
}
add_filter( 'user_has_cap', 'forumax_grant_bbpress_caps_to_admins', 10, 3 );

/**
 * Mutual Deactivation of old and new plugin paths
 */
add_action( 'activated_plugin', function( $plugin ) {
    if ( ! function_exists( 'is_plugin_active' ) ) {
        require_once ABSPATH . 'wp-admin/includes/plugin.php';
    }

    $free_plugins_new = [ 'forumax/forumax.php' ];
    $free_plugins_old = [ 'bbp-core/bbp-core.php', 'bbp-core/forumax.php' ];
    
    $pro_plugins_new  = [ 'forumax-pro/forumax.php' ];
    $pro_plugins_old  = [ 'bbp-core-pro/bbp-core.php', 'forumax-premium/bbp-core.php', 'forumax-premium/forumax.php' ];

    // If activating NEW free, deactivate OLD free
    if ( in_array( $plugin, $free_plugins_new, true ) ) {
        deactivate_plugins( $free_plugins_old );
    }

    // If activating OLD free, deactivate NEW free
    if ( in_array( $plugin, $free_plugins_old, true ) ) {
        deactivate_plugins( $free_plugins_new );
    }
    
    // If activating NEW pro, deactivate OLD pro
    if ( in_array( $plugin, $pro_plugins_new, true ) ) {
        deactivate_plugins( $pro_plugins_old );
    }

    // If activating OLD pro, deactivate NEW pro
    if ( in_array( $plugin, $pro_plugins_old, true ) ) {
        deactivate_plugins( $pro_plugins_new );
    }
} );

/**
 * Register Forum Sidebar widget area
 * This makes the Forum Sidebar available for any theme, not just theme-specific implementations
 */
add_action( 'widgets_init', function () {
    global $wp_registered_sidebars;
    
    // Check if the sidebar is already registered by the theme (e.g., Docy)
    if ( isset( $wp_registered_sidebars['forum_archive_sidebar'] ) ) {
        return;
    }
    
    register_sidebar( [
        'name'          => esc_html__( 'Forumax Sidebar', 'forumax' ),
        'description'   => esc_html__( 'Add widgets here for the Forumax Sidebar area', 'forumax' ),
        'id'            => 'forum_archive_sidebar',
        'before_widget' => '<div id="%1$s" class="widget sidebar_widget %2$s">',
        'after_widget'  => '</div>',
        'before_title'  => '<h3 class="widget-title">',
        'after_title'   => '</h3>'
    ] );
}, 20 ); // Priority 20 to run after theme's widgets_init


/**
 * Add title and thumbnail support for bbPress forum and topic post types.
 *
 * Ensures the title field and featured image meta box are available
 * in the WordPress admin editor for both post types.
 */
add_action( 'init', function () {
	add_post_type_support( 'forum', 'title' );
	add_post_type_support( 'forum', 'thumbnail' );
	add_post_type_support( 'topic', 'title' );
	add_post_type_support( 'topic', 'thumbnail' );
}, 25 );


/**
 * Get moderator and keymaster users for assistant selection.
 *
 * @return array Array of user ID => display name.
 */
if ( ! function_exists( 'frmx_get_moderator_users' ) ) {
    function frmx_get_moderator_users() {
        $users = [];

        // Get users with bbPress moderator or keymaster roles.
        $args = [
            'role__in' => [ 'bbp_moderator', 'bbp_keymaster', 'administrator' ],
            'orderby'  => 'display_name',
            'order'    => 'ASC',
            'number'   => 100,
        ];

        $user_query = new WP_User_Query( $args );

        if ( ! empty( $user_query->get_results() ) ) {
            foreach ( $user_query->get_results() as $user ) {
				
                $role_display = '';

                if ( in_array( 'bbp_keymaster', (array) $user->roles, true ) ) {
                    $role_display = __( 'Keymaster', 'forumax' );
                } elseif ( in_array( 'bbp_moderator', (array) $user->roles, true ) ) {
                    $role_display = __( 'Moderator', 'forumax' );
                } elseif ( in_array( 'administrator', (array) $user->roles, true ) ) {
                    $role_display = __( 'Admin', 'forumax' );
                }

                $users[$user->ID] = sprintf(
                    '%s (%s)',
                    $user->display_name,
                    $role_display
                );
            }
        }

        return $users;
    }
}

/**
 * Remove all admin notices on Forumax admin pages.
 *
 * Cleans up the admin interface by hiding third-party notices
 * on all Forumax-related admin pages.
 *
 * @since 1.0.0
 * @return void
 */
function forumax_remove_admin_notices() {
	// Check if we're on any Forumax admin page.
	$is_forumax_page = false;

	// Check using forumax_admin_pages() for known page types.
	if ( function_exists( 'forumax_admin_pages' ) ) {
		$is_forumax_page = forumax_admin_pages( 'admin' ) 
			|| forumax_admin_pages( 'settings' ) 
			|| forumax_admin_pages( 'dashboard' );
	}

	// Also check for Analytics and other forumax-* pages.
	$current_page = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : '';
	if ( strpos( $current_page, 'forumax' ) === 0 ) {
		$is_forumax_page = true;
	}

	if ( $is_forumax_page ) {
		remove_all_actions( 'admin_notices' );
		remove_all_actions( 'all_admin_notices' );
	}
}
add_action( 'admin_head', 'forumax_remove_admin_notices' );

/**
 * Hide theme sidebars on bbPress pages
 * 
 * This ensures only the Forumax sidebar displays on forum pages,
 * preventing conflicts with theme sidebars (Divi, Astra, etc.).
 * 
 * @param array $sidebars_widgets Array of sidebar widgets.
 * @return array Modified array with theme sidebars emptied on bbPress pages.
 */
function forumax_hide_theme_sidebars( $sidebars_widgets ) {
    // Only modify on frontend bbPress pages
	if ( is_admin() || ! function_exists( 'is_bbpress' ) || ! is_bbpress() ) {
		return $sidebars_widgets;
	}

	// Keep only the Forumax sidebar, hide all other theme sidebars
	foreach ( $sidebars_widgets as $sidebar_id => $widgets ) {
		// Skip wp_inactive_widgets and our own forum sidebar
		// We also skip footer sidebars to ensure footer widgets are visible
		if ( 'wp_inactive_widgets' === $sidebar_id || 'forum_archive_sidebar' === $sidebar_id || strpos( $sidebar_id, 'footer' ) !== false ) {
			continue;
		}

		// Empty other sidebars (this prevents them from rendering)
		$sidebars_widgets[ $sidebar_id ] = [];
	}
    
    return $sidebars_widgets;
}
add_filter( 'sidebars_widgets', 'forumax_hide_theme_sidebars' );

/**
 * Get topic count by status.
 *
 * @param string $status The post status to count (e.g., 'publish', 'closed').
 * @param int|bool $parent_id Optional. The parent forum ID to filter by.
 *
 * @return int The number of topics with the specified status.
 */
function forumax_get_topic_count_by_status( $status = 'publish', $parent_id = false ) {
    global $wpdb;

    // Sanitize parameters for cache key
    $cache_status = sanitize_key( $status );
    $cache_parent = ( false !== $parent_id && is_numeric( $parent_id ) ) ? (int) $parent_id : 0;

    // Check transient cache
    $cache_key = 'frmx_topic_count_' . $cache_status . '_' . $cache_parent;
    $count     = get_transient( $cache_key );

    if ( false !== $count ) {
        return (int) $count;
    }

    $status = esc_sql( $status );
    $where  = "WHERE post_type = 'topic' AND post_status = '{$status}'";

    if ( false !== $parent_id && is_numeric( $parent_id ) ) {
        $where .= $wpdb->prepare( " AND post_parent = %d", $parent_id );
    }

    $count = $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->posts} {$where}" );

    // Set transient cache for 1 hour
    set_transient( $cache_key, (int) $count, HOUR_IN_SECONDS );

    return (int) $count;
}

/**
 * Get unanswered topics count.
 *
 * @return int The number of unanswered topics.
 */
function forumax_get_unanswered_topics_count() {
    $cache_key = 'frmx_unanswered_topics_count';
    $count     = get_transient( $cache_key );

    if ( false !== $count ) {
        return $count;
    }

    global $wpdb;
    $count = $wpdb->get_var(
        "SELECT COUNT(*) FROM {$wpdb->posts} p
         LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = '_bbp_reply_count'
         WHERE p.post_type = 'topic'
         AND p.post_status = 'publish'
         AND (pm.meta_value IS NULL OR pm.meta_value = '0')"
    );

    set_transient( $cache_key, $count, HOUR_IN_SECONDS );
    return absint( $count );
}

/**
 * Get recent unanswered topics.
 *
 * @param int $limit Number of topics to retrieve.
 * @return array Array of WP_Post objects.
 */
function forumax_get_recent_unanswered_topics( $limit = 5 ) {
    $args = [
        'post_type'      => 'topic',
        'post_status'    => 'publish',
        'posts_per_page' => $limit,
        'orderby'        => 'date',
        'order'          => 'DESC',
        'meta_query'     => [
            'relation' => 'OR',
            [
                'key'     => '_bbp_reply_count',
                'value'   => '0',
                'compare' => '=',
            ],
            [
                'key'     => '_bbp_reply_count',
                'compare' => 'NOT EXISTS',
            ],
        ],
    ];

    return get_posts( $args );
}

/**
 * Invalidate unanswered topics cache.
 */
function forumax_invalidate_unanswered_topics_cache() {
    delete_transient( 'frmx_unanswered_topics_count' );
}
add_action( 'bbp_new_reply', 'forumax_invalidate_unanswered_topics_cache' );
add_action( 'bbp_deleted_reply', 'forumax_invalidate_unanswered_topics_cache' );
add_action( 'bbp_trash_reply', 'forumax_invalidate_unanswered_topics_cache' );
add_action( 'bbp_untrash_reply', 'forumax_invalidate_unanswered_topics_cache' );
add_action( 'bbp_spam_reply', 'forumax_invalidate_unanswered_topics_cache' );
add_action( 'bbp_unspam_reply', 'forumax_invalidate_unanswered_topics_cache' );
add_action( 'bbp_new_topic', 'forumax_invalidate_unanswered_topics_cache' );
add_action( 'bbp_deleted_topic', 'forumax_invalidate_unanswered_topics_cache' );
add_action( 'bbp_trash_topic', 'forumax_invalidate_unanswered_topics_cache' );
add_action( 'bbp_untrash_topic', 'forumax_invalidate_unanswered_topics_cache' );
add_action( 'bbp_spam_topic', 'forumax_invalidate_unanswered_topics_cache' );
add_action( 'bbp_unspam_topic', 'forumax_invalidate_unanswered_topics_cache' );

/**
 * Filter topics by unanswered status in admin dashboard.
 *
 * @param WP_Query $query The WP_Query instance (modified in place).
 */
function forumax_filter_unanswered_topics_admin( $query ) {
	if ( ! is_admin() || ! $query->is_main_query() ) {
		return;
	}

	if ( 'topic' !== $query->get( 'post_type' ) ) {
		return;
	}

	$forumax_filter = '';
	if ( isset( $_GET['forumax_filter'] ) ) {
		$forumax_filter = sanitize_key( wp_unslash( $_GET['forumax_filter'] ) );
	}

	if ( 'unanswered' === $forumax_filter ) {
		$meta_query = $query->get( 'meta_query' );
		if ( ! is_array( $meta_query ) ) {
			$meta_query = [];
		}

		$meta_query[] = [
			'relation' => 'OR',
			[
				'key'     => '_bbp_reply_count',
				'value'   => '0',
				'compare' => '=',
			],
			[
				'key'     => '_bbp_reply_count',
				'compare' => 'NOT EXISTS',
			],
		];

		$query->set( 'meta_query', $meta_query );
	}
}
add_action( 'pre_get_posts', 'forumax_filter_unanswered_topics_admin' );

```
