PluginProbe
Forumax – AI Powered Advanced Community Forum Plugin / 2.2.0
Forumax – AI Powered Advanced Community Forum Plugin v2.2.0
2.4.4 2.4.3 2.4.2 2.4.1 2.4.0 trunk 1.0.8 1.1.0 1.2.1 1.2.2 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3.0 1.3.1 1.3.2 1.3.3 1.4.0 1.4.1 2.0.0 2.1.0 All 29 releases
← All changes | includes/functions.php +701 -247 2.0.02.2.0 View file →
@@ -1,247 +1,701 @@
1 -<?php
2 -/**
3 - * Get the value of a settings field.
4 - *
5 - * @param string $option settings field name
6 - * @param string $section the section name this field belongs to
7 - * @param string $default default text if it's not found
8 - *
9 - * @return mixed
10 - */
11 -function forumax_get_opt( $option, $default = '' ) {
12 - $options = get_option( 'bbp_core_settings' );
13 -
14 - if ( isset( $options[ $option ] ) ) {
15 - return $options[ $option ];
16 - }
17 -
18 - return $default;
19 -}
20 -
21 -/**
22 - * Check if a plugin has been installed for specific number of days
23 - *
24 - * @param string $plugin_path The plugin path (e.g. 'woocommerce/woocommerce.php')
25 - * @param int $days Number of days to check against
26 - * @return bool True if plugin is installed for specified days, false otherwise
27 - */
28 -function forumax_is_plugin_installed_for_days( $days, $plugin_slug='eazydocs' ) {
29 - // Get the installation timestamp of the plugin
30 - $installed_time = get_option( $plugin_slug . '_installed' );
31 -
32 - // Ensure it's a valid timestamp
33 - if ( ! is_numeric( $installed_time ) || $installed_time <= 0 ) {
34 - return false;
35 - }
36 -
37 - // Convert days to seconds
38 - $required_time = (int) $days * DAY_IN_SECONDS;
39 -
40 - // Get the current UTC time
41 - $current_time = time();
42 -
43 - // Check if the plugin has been installed for the required duration
44 - return ( $current_time - $installed_time ) >= $required_time;
45 -}
46 -
47 -/**
48 - * Check If the Page is Forum page
49 - */
50 -function forumax_is_forum_page() {
51 - if ( in_array( 'bbpress', get_body_class() ) ) {
52 - return true;
53 - }
54 -}
55 -
56 -/**
57 - * Check if the pro plugin and plan is active
58 - *
59 - * @return bool|void
60 - */
61 -function forumax_is_premium() {
62 - if ( class_exists('Forumax_Pro') && bc_fs()->can_use_premium_code() ) {
63 - return true;
64 - }
65 -}
66 -
67 -/**
68 - * Forumax Admin pages
69 - * If any of the admin pages match the current page, return true.
70 - *
71 - * @return bool|void
72 - */
73 -function forumax_admin_pages($admin) {
74 - $current_url = ! empty( $_GET['page'] ) ? admin_url( 'admin.php?page=' ) . sanitize_text_field( wp_unslash( $_GET['page'] ) ) : '';
75 -
76 - if ( $admin == 'admin' ){
77 - if ( $current_url == admin_url('admin.php?page=forumax') ) {
78 - return true;
79 - }
80 - } elseif ( $admin == 'settings' ) {
81 - if ( $current_url == admin_url('admin.php?page=forumax-settings') ) {
82 - return true;
83 - }
84 - }
85 -}
86 -
87 -/**
88 - * BBP Forum Assets
89 - * Checks if the current page is a single forum or a single topic.
90 - *
91 - * @return bool True if the current page is a single forum or topic, false otherwise.
92 - */
93 -function forumax_forum_and_topic_page(){
94 - if ( bbp_is_single_forum() || bbp_is_single_topic() || bbp_is_reply_edit()) {
95 - return true;
96 - }
97 -}
98 -
99 -
100 -/**
101 - * Posts Arraty
102 - * @param object Post Type
103 - */
104 -function forumax_get_posts( $post_type = 'forum' ) {
105 - $posts = get_pages(
106 - [
107 - 'post_type' => $post_type,
108 - 'parent' => 0,
109 - ]
110 - );
111 -
112 - $posts_array = [];
113 -
114 - if ( $posts ) {
115 - foreach ( $posts as $post ) {
116 - $posts_array[ $post->ID ] = $post->post_title;
117 - }
118 - }
119 -
120 - return $posts_array;
121 -}
122 -
123 -/**
124 - * Limit letter
125 - * @param $string
126 - * @param $limit_length
127 - * @param string $suffix
128 - */
129 -function forumax_limit_letter( $string, $limit_length, $suffix = '...' ) {
130 - if ( strlen( $string ) > $limit_length ) {
131 - echo esc_html ( strip_shortcodes( substr( $string, 0, $limit_length ) . $suffix ) );
132 - } else {
133 - echo esc_html( $string );
134 - }
135 -}
136 -
137 -/**
138 - * Return the topic view count.
139 - *
140 - * @param int $topic_id Optional. Topic id
141 - *
142 - * @return int The view count
143 - * @uses get_post_meta() To get the view count meta
144 - * @uses bbp_get_topic_id() To get the topic id
145 - */
146 -function forumax_get_topic_view_count( $topic_id = 0 ) {
147 - $topic_id = bbp_get_topic_id( $topic_id );
148 -
149 - if ( empty( $topic_id ) ) {
150 - return 0;
151 - }
152 -
153 - $views = (int) get_post_meta( $topic_id, '_btv_view_count', true );
154 -
155 - return $views;
156 -}
157 -
158 -/**
159 - * Output the topic view count.
160 - *
161 - * @param int $topic_id Optional. Topic id
162 - *
163 - * @uses bbp_get_topic_id() To get the topic id
164 - * @uses btv_get_topic_view_count() To get the view count for the topic
165 - */
166 -function forumax_topic_view_count( $topic_id = 0 ) {
167 - $topic_id = bbp_get_topic_id( $topic_id );
168 - $view_count = forumax_get_topic_view_count( $topic_id );
169 - return $view_count;
170 -}
171 -
172 -/**
173 - * Get forum title
174 - * @return string
175 - */
176 -function forumax_forum_title(){
177 - $forum_id = bbp_get_forum_id();
178 - $forum_title = get_the_title( $forum_id );
179 - return $forum_title;
180 -}
181 -
182 -/**
183 - * Customizer section hide from customizer
184 - */
185 -add_action( 'customize_register', function( $wp_customize ) {
186 - // Unset the section you want to hide
187 - $wp_customize->remove_section( 'design_fields' );
188 -}, 20 );
189 -
190 -/**
191 - * Get all the registered menus
192 - */
193 -function forumax_get_registered_nav_menus() {
194 - $menus = get_registered_nav_menus();
195 - $menu_locations = [];
196 - $empty = [ '' => esc_html__('Select Menu Location', 'forumax') ];
197 - foreach ( $menus as $location => $description ) {
198 - $menu_locations[ $location ] = $description;
199 - }
200 -
201 - return $empty + $menu_locations;
202 -}
203 -
204 -/**
205 - * Enable REST API for bbPress forum post type
206 - * This allows the forum post type to be accessed via the WordPress REST API
207 - * which is required for the Gutenberg block to fetch forum data
208 - */
209 -function forumax_enable_forum_rest_api() {
210 - $post_type_obj = get_post_type_object( 'forum' );
211 - if ( $post_type_obj ) {
212 - $post_type_obj->show_in_rest = true;
213 - }
214 -}
215 -add_action( 'init', 'forumax_enable_forum_rest_api', 20 );
216 -
217 -/**
218 - * Mutual Deactivation of Forumax and bbp-core plugins
219 - */
220 -add_action( 'activated_plugin', function( $plugin ) {
221 - if ( ! function_exists( 'is_plugin_active' ) ) {
222 - require_once ABSPATH . 'wp-admin/includes/plugin.php';
223 - }
224 -
225 - // All Forumax plugins
226 - $forumax_plugins = array(
227 - 'forumax/forumax.php',
228 - 'forumax-pro/forumax.php',
229 - );
230 -
231 - // All bbp-core plugins
232 - $bbp_core_plugins = array(
233 - 'bbp-core/bbp-core.php',
234 - 'bbp-core-pro/bbp-core.php',
235 - );
236 -
237 - // If activating Forumax (free or pro) → deactivate bbp-core (all versions)
238 - if ( in_array( $plugin, $forumax_plugins, true ) ) {
239 - deactivate_plugins( $bbp_core_plugins );
240 - }
241 -
242 - // If activating bbp-core (free or pro) → deactivate Forumax (all versions)
243 - if ( in_array( $plugin, $bbp_core_plugins, true ) ) {
244 - deactivate_plugins( $forumax_plugins );
245 - }
246 -} );
247 -
1 +<?php
2 +/**
3 + * Get the value of a settings field.
4 + *
5 + * @param string $option settings field name
6 + * @param string $section the section name this field belongs to
7 + * @param string $default default text if it's not found
8 + *
9 + * @return mixed
10 + */
11 +function forumax_get_opt( $option, $default = '' ) {
12 + $options = get_option( 'bbp_core_settings' );
13 +
14 + if ( isset( $options[ $option ] ) ) {
15 + return $options[ $option ];
16 + }
17 +
18 + return $default;
19 +}
20 +
21 +/**
22 + * Check if a plugin has been installed for specific number of days
23 + *
24 + * @param string $plugin_path The plugin path (e.g. 'woocommerce/woocommerce.php')
25 + * @param int $days Number of days to check against
26 + * @return bool True if plugin is installed for specified days, false otherwise
27 + */
28 +function forumax_is_plugin_installed_for_days( $days, $plugin_slug='eazydocs' ) {
29 + // Get the installation timestamp of the plugin
30 + $installed_time = get_option( $plugin_slug . '_installed' );
31 +
32 + // Ensure it's a valid timestamp
33 + if ( ! is_numeric( $installed_time ) || $installed_time <= 0 ) {
34 + return false;
35 + }
36 +
37 + // Convert days to seconds
38 + $required_time = (int) $days * DAY_IN_SECONDS;
39 +
40 + // Get the current UTC time
41 + $current_time = time();
42 +
43 + // Check if the plugin has been installed for the required duration
44 + return ( $current_time - $installed_time ) >= $required_time;
45 +}
46 +
47 +/**
48 + * Check If the Page is Forum page
49 + */
50 +function forumax_is_forum_page() {
51 + if ( in_array( 'bbpress', get_body_class(), true ) ) {
52 + return true;
53 + }
54 +}
55 +
56 +/**
57 + * Turn on the WordPress visual editor for bbPress
58 + *
59 + * @param array $args
60 + * @return array
61 + */
62 +function forumax_bbp_enable_visual_editor( $args = [] ) {
63 + $args['tinymce'] = true;
64 + return $args;
65 +}
66 +add_filter( 'bbp_after_get_the_content_parse_args', 'forumax_bbp_enable_visual_editor' );
67 +
68 +/**
69 + * Check if the pro plugin and plan is active
70 + *
71 + * @return bool|void
72 + */
73 +function forumax_is_premium() {
74 + if ( class_exists('Forumax_Pro') && bc_fs()->can_use_premium_code() ) {
75 + return true;
76 + }
77 +}
78 +
79 +/**
80 + * Check if the promax plan is active
81 + *
82 + * @return bool|void
83 + */
84 +function forumax_is_promax() {
85 + if ( class_exists('Forumax_Pro') && bc_fs()->can_use_premium_code() && bc_fs()->is_plan('promax') ) {
86 + return true;
87 + }
88 +}
89 +
90 +/**
91 + * Forumax Admin pages
92 + *
93 + * Checks if the current admin page matches the specified Forumax page type.
94 + *
95 + * @param string $admin The admin page type to check ('admin', 'settings', 'dashboard').
96 + *
97 + * @return bool True if on the specified admin page, false otherwise.
98 + */
99 +function forumax_admin_pages( $admin ) {
100 + $current_url = ! empty( $_GET['page'] ) ? admin_url( 'admin.php?page=' ) . sanitize_text_field( wp_unslash( $_GET['page'] ) ) : '';
101 +
102 + if ( 'admin' === $admin ) {
103 + if ( admin_url( 'admin.php?page=forumax-builder' ) === $current_url ) {
104 + return true;
105 + }
106 + } elseif ( 'settings' === $admin ) {
107 + if ( admin_url( 'admin.php?page=forumax-settings' ) === $current_url ) {
108 + return true;
109 + }
110 + } elseif ( 'dashboard' === $admin ) {
111 + if ( admin_url( 'admin.php?page=forumax' ) === $current_url ) {
112 + return true;
113 + }
114 + }
115 +
116 + return false;
117 +}
118 +
119 +
120 +/**
121 + * BBP Forum Assets
122 + * Checks if the current page is a single forum or a single topic.
123 + *
124 + * @return bool True if the current page is a single forum or topic, false otherwise.
125 + */
126 +function forumax_forum_and_topic_page(){
127 + if ( bbp_is_single_forum() || bbp_is_single_topic() || bbp_is_reply_edit()) {
128 + return true;
129 + }
130 +}
131 +
132 +
133 +/**
134 + * Posts Arraty
135 + * @param object Post Type
136 + */
137 +function forumax_get_posts( $post_type = 'forum' ) {
138 + $posts = get_pages(
139 + [
140 + 'post_type' => $post_type,
141 + 'parent' => 0,
142 + ]
143 + );
144 +
145 + $posts_array = [];
146 +
147 + if ( $posts ) {
148 + foreach ( $posts as $post ) {
149 + $posts_array[ $post->ID ] = $post->post_title;
150 + }
151 + }
152 +
153 + return $posts_array;
154 +}
155 +
156 +/**
157 + * Limit letter
158 + * @param $string
159 + * @param $limit_length
160 + * @param string $suffix
161 + */
162 +function forumax_limit_letter( $string, $limit_length, $suffix = '...' ) {
163 + if ( strlen( $string ) > $limit_length ) {
164 + echo esc_html ( strip_shortcodes( substr( $string, 0, $limit_length ) . $suffix ) );
165 + } else {
166 + echo esc_html( $string );
167 + }
168 +}
169 +
170 +/**
171 + * Return the topic view count.
172 + *
173 + * @param int $topic_id Optional. Topic id
174 + *
175 + * @return int The view count
176 + * @uses get_post_meta() To get the view count meta
177 + * @uses bbp_get_topic_id() To get the topic id
178 + */
179 +function forumax_get_topic_view_count( $topic_id = 0 ) {
180 + $topic_id = bbp_get_topic_id( $topic_id );
181 +
182 + if ( empty( $topic_id ) ) {
183 + return 0;
184 + }
185 +
186 + $views = (int) get_post_meta( $topic_id, '_btv_view_count', true );
187 +
188 + return $views;
189 +}
190 +
191 +/**
192 + * Output the topic view count.
193 + *
194 + * @param int $topic_id Optional. Topic id
195 + *
196 + * @uses bbp_get_topic_id() To get the topic id
197 + * @uses btv_get_topic_view_count() To get the view count for the topic
198 + */
199 +function forumax_topic_view_count( $topic_id = 0 ) {
200 + $topic_id = bbp_get_topic_id( $topic_id );
201 + $view_count = forumax_get_topic_view_count( $topic_id );
202 + return $view_count;
203 +}
204 +
205 +/**
206 + * Increment the topic view count.
207 + *
208 + * Increments the view count when a visitor views a topic.
209 + * Uses cookies to prevent duplicate counts within a session (1 hour).
210 + *
211 + * @param int $topic_id Topic ID.
212 + * @return bool True if view was counted, false otherwise.
213 + */
214 +function forumax_increment_topic_view_count( $topic_id = 0 ) {
215 + $topic_id = bbp_get_topic_id( $topic_id );
216 +
217 + if ( empty( $topic_id ) ) {
218 + return false;
219 + }
220 +
221 + // Optionally: Don't count views for logged-in admins/moderators
222 + // Uncomment the following to exclude admin views from being counted
223 + // if ( current_user_can( 'moderate' ) ) {
224 + // return false;
225 + // }
226 +
227 + // Use a cookie to prevent duplicate counts within the same session
228 + $cookie_name = 'forumax_viewed_' . $topic_id;
229 +
230 + // Check if this topic was already viewed in this session
231 + if ( isset( $_COOKIE[ $cookie_name ] ) ) {
232 + return false;
233 + }
234 +
235 + // Get current view count
236 + $current_views = (int) get_post_meta( $topic_id, '_btv_view_count', true );
237 +
238 + // Increment the view count
239 + $new_views = $current_views + 1;
240 +
241 + // Update the view count
242 + update_post_meta( $topic_id, '_btv_view_count', $new_views );
243 +
244 + // Set cookie to prevent duplicate counting (expires in 1 hour)
245 + setcookie( $cookie_name, '1', time() + HOUR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN, is_ssl(), true );
246 +
247 + return true;
248 +}
249 +
250 +/**
251 + * Track topic views on template redirect.
252 + *
253 + * Hooks into bbPress template redirect to track topic views
254 + * when a single topic page is loaded.
255 + */
256 +function forumax_track_topic_views() {
257 + // Only track on single topic pages
258 + if ( ! function_exists( 'bbp_is_single_topic' ) || ! bbp_is_single_topic() ) {
259 + return;
260 + }
261 +
262 + // Get the current topic ID
263 + $topic_id = bbp_get_topic_id();
264 +
265 + if ( ! empty( $topic_id ) ) {
266 + forumax_increment_topic_view_count( $topic_id );
267 + }
268 +}
269 +add_action( 'bbp_template_redirect', 'forumax_track_topic_views', 20 );
270 +
271 +/**
272 + * Get forum title
273 + * @return string
274 + */
275 +function forumax_forum_title(){
276 + $forum_id = bbp_get_forum_id();
277 + $forum_title = get_the_title( $forum_id );
278 + return $forum_title;
279 +}
280 +
281 +/**
282 + * Customizer section hide from customizer
283 + */
284 +add_action( 'customize_register', function( $wp_customize ) {
285 + // Unset the section you want to hide
286 + $wp_customize->remove_section( 'design_fields' );
287 +}, 20 );
288 +
289 +/**
290 + * Get all the registered menus
291 + */
292 +function forumax_get_registered_nav_menus() {
293 + $menus = get_registered_nav_menus();
294 + $menu_locations = [];
295 + $empty = [ '' => esc_html__('Select Menu Location', 'forumax') ];
296 + foreach ( $menus as $location => $description ) {
297 + $menu_locations[ $location ] = $description;
298 + }
299 +
300 + return $empty + $menu_locations;
301 +}
302 +
303 +/**
304 + * Fix Gutenberg editor support for bbPress forum and topic post types.
305 + *
306 + * Filters the post type registration arguments to enable full REST API
307 + * support, ensure thumbnail support, and force correct labels so that
308 + * Gutenberg displays the proper post type name instead of "Document".
309 + *
310 + * @param array $args Array of arguments for registering a post type.
311 + * @param string $post_type Post type key.
312 + * @return array Modified arguments.
313 + */
314 +function forumax_fix_post_type_args( $args, $post_type ) {
315 + $post_types_config = array(
316 + 'forum' => array(
317 + 'rest_base' => 'forums',
318 + 'labels' => array(
319 + 'name' => __( 'Forums', 'bbpress' ),
320 + 'singular_name' => __( 'Forum', 'bbpress' ),
321 + ),
322 + ),
323 + 'topic' => array(
324 + 'rest_base' => 'topics',
325 + 'labels' => array(
326 + 'name' => __( 'Topics', 'bbpress' ),
327 + 'singular_name' => __( 'Topic', 'bbpress' ),
328 + ),
329 + ),
330 + );
331 +
332 + if ( ! isset( $post_types_config[ $post_type ] ) ) {
333 + return $args;
334 + }
335 +
336 + $config = $post_types_config[ $post_type ];
337 +
338 + // Force full REST API support for Gutenberg.
339 + $args['show_in_rest'] = true;
340 + $args['rest_base'] = $config['rest_base'];
341 + $args['rest_controller_class'] = 'WP_REST_Posts_Controller';
342 +
343 + // Set explicit label property (singular string).
344 + $args['label'] = $config['labels']['name'];
345 +
346 + // Ensure thumbnail support is included.
347 + if ( ! empty( $args['supports'] ) && is_array( $args['supports'] ) ) {
348 + if ( ! in_array( 'thumbnail', $args['supports'], true ) ) {
349 + $args['supports'][] = 'thumbnail';
350 + }
351 + } else {
352 + $args['supports'] = array( 'title', 'editor', 'revisions', 'thumbnail' );
353 + }
354 +
355 + // Merge labels to prevent Gutenberg "Document" fallback.
356 + if ( ! empty( $args['labels'] ) && is_array( $args['labels'] ) ) {
357 + $args['labels'] = array_merge( $args['labels'], $config['labels'] );
358 + } else {
359 + $args['labels'] = $config['labels'];
360 + }
361 +
362 + return $args;
363 +}
364 +add_filter( 'register_post_type_args', 'forumax_fix_post_type_args', 99, 2 );
365 +
366 +/**
367 + * Grant bbPress post type capabilities to WordPress administrators.
368 + *
369 + * Gutenberg requests the post type REST endpoint with context=edit, which
370 + * checks custom capabilities like edit_forums and edit_topics. WordPress
371 + * administrators may not have these caps unless bbPress has explicitly
372 + * assigned them. This filter dynamically grants the required caps to
373 + * any user who can manage_options (i.e. administrators).
374 + *
375 + * @param array $allcaps All capabilities for the user.
376 + * @param array $caps Required capabilities being checked.
377 + * @param array $args Additional arguments passed to the check.
378 + * @return array Modified capabilities array.
379 + */
380 +function forumax_grant_bbpress_caps_to_admins( $allcaps, $caps, $args ) {
381 + // Only grant to users who can manage options (administrators).
382 + if ( empty( $allcaps['manage_options'] ) ) {
383 + return $allcaps;
384 + }
385 +
386 + // bbPress forum and topic capabilities needed for REST API access.
387 + $bbpress_caps = array(
388 + 'edit_forums',
389 + 'edit_others_forums',
390 + 'publish_forums',
391 + 'read_private_forums',
392 + 'read_hidden_forums',
393 + 'delete_forums',
394 + 'delete_others_forums',
395 + 'edit_topics',
396 + 'edit_others_topics',
397 + 'publish_topics',
398 + 'read_private_topics',
399 + 'delete_topics',
400 + 'delete_others_topics',
401 + );
402 +
403 + foreach ( $bbpress_caps as $cap ) {
404 + $allcaps[ $cap ] = true;
405 + }
406 +
407 + return $allcaps;
408 +}
409 +add_filter( 'user_has_cap', 'forumax_grant_bbpress_caps_to_admins', 10, 3 );
410 +
411 +/**
412 + * Mutual Deactivation of Forumax and bbp-core plugins
413 + */
414 +add_action( 'activated_plugin', function( $plugin ) {
415 + if ( ! function_exists( 'is_plugin_active' ) ) {
416 + require_once ABSPATH . 'wp-admin/includes/plugin.php';
417 + }
418 +
419 + // All Forumax plugins
420 + $forumax_plugins = [
421 + 'forumax/forumax.php',
422 + 'forumax-pro/forumax.php',
423 + ];
424 +
425 + // All bbp-core plugins
426 + $bbp_core_plugins = [
427 + 'bbp-core/bbp-core.php',
428 + 'bbp-core-pro/bbp-core.php',
429 + ];
430 +
431 + // If activating Forumax (free or pro) → deactivate bbp-core (all versions)
432 + if ( in_array( $plugin, $forumax_plugins, true ) ) {
433 + deactivate_plugins( $bbp_core_plugins );
434 + }
435 +
436 +
437 + // If activating bbp-core (free or pro) → deactivate Forumax (all versions)
438 + if ( in_array( $plugin, $bbp_core_plugins, true ) ) {
439 + deactivate_plugins( $forumax_plugins );
440 + }
441 +
442 +} );
443 +
444 +/**
445 + * Register Forum Sidebar widget area
446 + * This makes the Forum Sidebar available for any theme, not just theme-specific implementations
447 + */
448 +add_action( 'widgets_init', function () {
449 + global $wp_registered_sidebars;
450 +
451 + // Check if the sidebar is already registered by the theme (e.g., Docy)
452 + if ( isset( $wp_registered_sidebars['forum_archive_sidebar'] ) ) {
453 + return;
454 + }
455 +
456 + register_sidebar( [
457 + 'name' => esc_html__( 'Forumax Sidebar', 'forumax' ),
458 + 'description' => esc_html__( 'Add widgets here for the Forumax Sidebar area', 'forumax' ),
459 + 'id' => 'forum_archive_sidebar',
460 + 'before_widget' => '<div id="%1$s" class="widget sidebar_widget %2$s">',
461 + 'after_widget' => '</div>',
462 + 'before_title' => '<h3 class="widget-title">',
463 + 'after_title' => '</h3>'
464 + ] );
465 +}, 20 ); // Priority 20 to run after theme's widgets_init
466 +
467 +
468 +/**
469 + * Add title and thumbnail support for bbPress forum and topic post types.
470 + *
471 + * Ensures the title field and featured image meta box are available
472 + * in the WordPress admin editor for both post types.
473 + */
474 +add_action( 'init', function () {
475 + add_post_type_support( 'forum', 'title' );
476 + add_post_type_support( 'forum', 'thumbnail' );
477 + add_post_type_support( 'topic', 'title' );
478 + add_post_type_support( 'topic', 'thumbnail' );
479 +}, 25 );
480 +
481 +
482 +/**
483 + * Get moderator and keymaster users for assistant selection.
484 + *
485 + * @return array Array of user ID => display name.
486 + */
487 +if ( ! function_exists( 'frmx_get_moderator_users' ) ) {
488 + function frmx_get_moderator_users() {
489 + $users = [];
490 +
491 + // Get users with bbPress moderator or keymaster roles.
492 + $args = [
493 + 'role__in' => [ 'bbp_moderator', 'bbp_keymaster', 'administrator' ],
494 + 'orderby' => 'display_name',
495 + 'order' => 'ASC',
496 + 'number' => 100,
497 + ];
498 +
499 + $user_query = new WP_User_Query( $args );
500 +
501 + if ( ! empty( $user_query->get_results() ) ) {
502 + foreach ( $user_query->get_results() as $user ) {
503 +
504 + $role_display = '';
505 +
506 + if ( in_array( 'bbp_keymaster', (array) $user->roles, true ) ) {
507 + $role_display = __( 'Keymaster', 'forumax' );
508 + } elseif ( in_array( 'bbp_moderator', (array) $user->roles, true ) ) {
509 + $role_display = __( 'Moderator', 'forumax' );
510 + } elseif ( in_array( 'administrator', (array) $user->roles, true ) ) {
511 + $role_display = __( 'Admin', 'forumax' );
512 + }
513 +
514 + $users[$user->ID] = sprintf(
515 + '%s (%s)',
516 + $user->display_name,
517 + $role_display
518 + );
519 + }
520 + }
521 +
522 + return $users;
523 + }
524 +}
525 +
526 +/**
527 + * Remove all admin notices on Forumax admin pages.
528 + *
529 + * Cleans up the admin interface by hiding third-party notices
530 + * on all Forumax-related admin pages.
531 + *
532 + * @since 1.0.0
533 + * @return void
534 + */
535 +function forumax_remove_admin_notices() {
536 + // Check if we're on any Forumax admin page.
537 + $is_forumax_page = false;
538 +
539 + // Check using forumax_admin_pages() for known page types.
540 + if ( function_exists( 'forumax_admin_pages' ) ) {
541 + $is_forumax_page = forumax_admin_pages( 'admin' )
542 + || forumax_admin_pages( 'settings' )
543 + || forumax_admin_pages( 'dashboard' );
544 + }
545 +
546 + // Also check for Analytics and other forumax-* pages.
547 + $current_page = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : '';
548 + if ( strpos( $current_page, 'forumax' ) === 0 ) {
549 + $is_forumax_page = true;
550 + }
551 +
552 + if ( $is_forumax_page ) {
553 + remove_all_actions( 'admin_notices' );
554 + remove_all_actions( 'all_admin_notices' );
555 + }
556 +}
557 +add_action( 'admin_head', 'forumax_remove_admin_notices' );
558 +
559 +/**
560 + * Hide theme sidebars on bbPress pages
561 + *
562 + * This ensures only the Forumax sidebar displays on forum pages,
563 + * preventing conflicts with theme sidebars (Divi, Astra, etc.).
564 + *
565 + * @param array $sidebars_widgets Array of sidebar widgets.
566 + * @return array Modified array with theme sidebars emptied on bbPress pages.
567 + */
568 +function forumax_hide_theme_sidebars( $sidebars_widgets ) {
569 + // Only modify on frontend bbPress pages
570 + if ( is_admin() || ! function_exists( 'is_bbpress' ) || ! is_bbpress() ) {
571 + return $sidebars_widgets;
572 + }
573 +
574 + // Keep only the Forumax sidebar, hide all other theme sidebars
575 + foreach ( $sidebars_widgets as $sidebar_id => $widgets ) {
576 + // Skip wp_inactive_widgets and our own forum sidebar
577 + // We also skip footer sidebars to ensure footer widgets are visible
578 + if ( 'wp_inactive_widgets' === $sidebar_id || 'forum_archive_sidebar' === $sidebar_id || strpos( $sidebar_id, 'footer' ) !== false ) {
579 + continue;
580 + }
581 +
582 + // Empty other sidebars (this prevents them from rendering)
583 + $sidebars_widgets[ $sidebar_id ] = [];
584 + }
585 +
586 + return $sidebars_widgets;
587 +}
588 +add_filter( 'sidebars_widgets', 'forumax_hide_theme_sidebars' );
589 +
590 +/**
591 + * Get topic count by status.
592 + *
593 + * @param string $status The post status to count (e.g., 'publish', 'closed').
594 + * @param int|bool $parent_id Optional. The parent forum ID to filter by.
595 + *
596 + * @return int The number of topics with the specified status.
597 + */
598 +function forumax_get_topic_count_by_status( $status = 'publish', $parent_id = false ) {
599 + global $wpdb;
600 +
601 + // Sanitize parameters for cache key
602 + $cache_status = sanitize_key( $status );
603 + $cache_parent = ( false !== $parent_id && is_numeric( $parent_id ) ) ? (int) $parent_id : 0;
604 +
605 + // Check transient cache
606 + $cache_key = 'frmx_topic_count_' . $cache_status . '_' . $cache_parent;
607 + $count = get_transient( $cache_key );
608 +
609 + if ( false !== $count ) {
610 + return (int) $count;
611 + }
612 +
613 + $status = esc_sql( $status );
614 + $where = "WHERE post_type = 'topic' AND post_status = '{$status}'";
615 +
616 + if ( false !== $parent_id && is_numeric( $parent_id ) ) {
617 + $where .= $wpdb->prepare( " AND post_parent = %d", $parent_id );
618 + }
619 +
620 + $count = $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->posts} {$where}" );
621 +
622 + // Set transient cache for 1 hour
623 + set_transient( $cache_key, (int) $count, HOUR_IN_SECONDS );
624 +
625 + return (int) $count;
626 +}
627 +
628 +/**
629 + * Get unanswered topics count.
630 + *
631 + * @return int The number of unanswered topics.
632 + */
633 +function forumax_get_unanswered_topics_count() {
634 + $cache_key = 'frmx_unanswered_topics_count';
635 + $count = wp_cache_get( $cache_key, 'forumax' );
636 +
637 + if ( false !== $count ) {
638 + return $count;
639 + }
640 +
641 + global $wpdb;
642 + $count = $wpdb->get_var(
643 + "SELECT COUNT(*) FROM {$wpdb->posts} p
644 + LEFT JOIN {$wpdb->postmeta} pm ON p.ID = pm.post_id AND pm.meta_key = '_bbp_reply_count'
645 + WHERE p.post_type = 'topic'
646 + AND p.post_status = 'publish'
647 + AND (pm.meta_value IS NULL OR pm.meta_value = '0')"
648 + );
649 +
650 + wp_cache_set( $cache_key, $count, 'forumax', HOUR_IN_SECONDS );
651 + return absint( $count );
652 +}
653 +
654 +/**
655 + * Get recent unanswered topics.
656 + *
657 + * @param int $limit Number of topics to retrieve.
658 + * @return array Array of WP_Post objects.
659 + */
660 +function forumax_get_recent_unanswered_topics( $limit = 5 ) {
661 + $args = [
662 + 'post_type' => 'topic',
663 + 'post_status' => 'publish',
664 + 'posts_per_page' => $limit,
665 + 'orderby' => 'date',
666 + 'order' => 'DESC',
667 + 'meta_query' => [
668 + 'relation' => 'OR',
669 + [
670 + 'key' => '_bbp_reply_count',
671 + 'value' => '0',
672 + 'compare' => '=',
673 + ],
674 + [
675 + 'key' => '_bbp_reply_count',
676 + 'compare' => 'NOT EXISTS',
677 + ],
678 + ],
679 + ];
680 +
681 + return get_posts( $args );
682 +}
683 +
684 +/**
685 + * Invalidate unanswered topics cache.
686 + */
687 +function forumax_invalidate_unanswered_topics_cache() {
688 + wp_cache_delete( 'frmx_unanswered_topics_count', 'forumax' );
689 +}
690 +add_action( 'bbp_new_reply', 'forumax_invalidate_unanswered_topics_cache' );
691 +add_action( 'bbp_deleted_reply', 'forumax_invalidate_unanswered_topics_cache' );
692 +add_action( 'bbp_trash_reply', 'forumax_invalidate_unanswered_topics_cache' );
693 +add_action( 'bbp_untrash_reply', 'forumax_invalidate_unanswered_topics_cache' );
694 +add_action( 'bbp_spam_reply', 'forumax_invalidate_unanswered_topics_cache' );
695 +add_action( 'bbp_unspam_reply', 'forumax_invalidate_unanswered_topics_cache' );
696 +add_action( 'bbp_new_topic', 'forumax_invalidate_unanswered_topics_cache' );
697 +add_action( 'bbp_deleted_topic', 'forumax_invalidate_unanswered_topics_cache' );
698 +add_action( 'bbp_trash_topic', 'forumax_invalidate_unanswered_topics_cache' );
699 +add_action( 'bbp_untrash_topic', 'forumax_invalidate_unanswered_topics_cache' );
700 +add_action( 'bbp_spam_topic', 'forumax_invalidate_unanswered_topics_cache' );
701 +add_action( 'bbp_unspam_topic', 'forumax_invalidate_unanswered_topics_cache' );