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
bbp-core / includes / functions.php

functions.php in Forumax – AI Powered Advanced Community Forum Plugin 2.2.0, at includes/functions.php

702 lines 20.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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' );
702