PluginProbe
Forumax – AI Powered Advanced Community Forum Plugin / 2.5.1
Forumax – AI Powered Advanced Community Forum Plugin v2.5.1
2.5.1 2.5.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 All 31 releases
← All changes | includes/admin/menu/admin_ui.php +140 -80 1.2.2 → 2.5.1 View file →
@@ -2,12 +2,63 @@
2 2 if ( ! class_exists( 'bbPress' ) ) {
3 3 return;
4 4 }
5 5
6 +if ( ! function_exists( 'forumax_admin_reply_stats' ) ) {
7 + /**
8 + * Reply counts and pending reply IDs for many topics in two queries.
9 + *
10 + * Replaces per-topic get_children()/WP_Query calls, which made this screen
11 + * run several queries per topic on large forums.
12 + *
13 + * @param int[] $topic_ids Topic IDs.
14 + * @return array{total: array<int,int>, pending: array<int,int[]>}
15 + */
16 + function forumax_admin_reply_stats( array $topic_ids ) {
17 + global $wpdb;
18 +
19 + $stats = [
20 + 'total' => [],
21 + 'pending' => [],
22 + ];
23 + $topic_ids = array_values( array_unique( array_filter( array_map( 'intval', $topic_ids ) ) ) );
24 +
25 + foreach ( array_chunk( $topic_ids, 500 ) as $chunk ) {
26 + $placeholders = implode( ',', array_fill( 0, count( $chunk ), '%d' ) );
27 + $args = array_merge( [ bbp_get_reply_post_type() ], $chunk );
28 +
29 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared
30 + $rows = $wpdb->get_results( $wpdb->prepare( "SELECT ID, post_parent, post_status FROM {$wpdb->posts} WHERE post_type = %s AND post_status IN ( 'publish', 'draft', 'pending' ) AND post_parent IN ( $placeholders ) ORDER BY post_date DESC", $args ) );
31 +
32 + foreach ( $rows as $row ) {
33 + $parent = (int) $row->post_parent;
34 + $stats['total'][ $parent ] = ( $stats['total'][ $parent ] ?? 0 ) + 1;
35 +
36 + if ( 'pending' === $row->post_status ) {
37 + $stats['pending'][ $parent ][] = (int) $row->ID;
38 + }
39 + }
40 + }
41 +
42 + // Pending reply excerpts and each topic's freshness link are rendered,
43 + // so load those posts together instead of one query each.
44 + $prime_ids = empty( $stats['pending'] ) ? [] : array_merge( ...array_values( $stats['pending'] ) );
45 + foreach ( $topic_ids as $topic_id ) {
46 + $prime_ids[] = (int) get_post_meta( $topic_id, '_bbp_last_active_id', true );
47 + }
48 + $prime_ids = array_filter( $prime_ids );
49 +
50 + if ( ! empty( $prime_ids ) ) {
51 + _prime_post_caches( $prime_ids, false, true );
52 + }
53 +
54 + return $stats;
55 + }
56 +}
57 +
6 58 $parent_forums = [];
7 59 $fcount = wp_count_posts( bbp_get_forum_post_type() );
8 60 $forum_count = (int) ( $fcount->publish + $fcount->hidden + $fcount->spam );
9 -$bbpc_opt = get_option( 'bbp_core_settings' );
10 61 ?>
11 62 <div class="wrap">
12 63 <div class="body-dark">
13 64 <?php
@@ -17,25 +68,20 @@
17 68 <main>
18 69 <div class="easydocs-sidebar-menu">
19 70 <div class="tab-container">
20 71 <?php
21 - $forum_query = new WP_Query(
22 - [
23 - 'post_type' => bbp_get_forum_post_type(),
24 - 'posts_per_page' => -1,
25 - 'post_parent' => 0,
26 - 'orderby' => 'menu_order',
27 - 'order' => 'ASC',
28 - 'post_status' => 'publish',
29 - ]
30 - );
31 -
72 + $forum_query = new WP_Query( [
73 + 'post_type' => bbp_get_forum_post_type(),
74 + 'posts_per_page' => -1,
75 + 'post_parent' => 0,
76 + 'orderby' => 'menu_order',
77 + 'order' => 'ASC',
78 + 'post_status' => 'publish',
79 + ] );
32 80 $count = $forum_query->found_posts;
33 -
34 81 // Left Sidebar Forums.
35 82 include __DIR__ . '/admin_ui/forums.php';
36 83 ?>
37 -
38 84 <div class="easydocs-tab-content">
39 85 <?php
40 86 $ids = 0;
41 87 $container = 1;
@@ -63,20 +109,13 @@
63 109 $count_solved = 0;
64 110 $count_unsolved = 0;
65 111 $count_trash = 0;
66 112
67 - while ( $children->have_posts() ) :
68 - $children->the_post();
113 + $reply_stats = forumax_admin_reply_stats( wp_list_pluck( $children->posts, 'ID' ) );
114 +
115 + while ( $children->have_posts() ) : $children->the_post();
69 116 $topic_id = get_the_ID();
70 117
71 - $replies = get_children(
72 - [
73 - 'post_parent' => $topic_id,
74 - 'post_type' => bbp_get_reply_post_type(),
75 - 'post_status' => [ 'publish', 'draft', 'pending' ],
76 - ]
77 - );
78 -
79 118 // Count open/closed topics.
80 119 if ( bbp_is_topic_closed( $topic_id ) ) {
81 120 $count_closed++;
82 121 } else {
@@ -83,88 +122,93 @@
83 122 $count_open++;
84 123 }
85 124
86 125 // Count spam( hidden ) topics.
87 - if ( bbp_is_topic_spam( $topic_id ) ) {
126 + if ( bbp_is_topic_spam( $topic_id ) || bbp_is_topic_pending( $topic_id ) ) {
88 127 $count_hidden++;
89 128 }
90 129
91 130 // Replies count.
92 - if ( 0 == count( $replies ) ) {
131 + if ( empty( $reply_stats['total'][ $topic_id ] ) ) {
93 132 $count_no_reply++;
94 133 }
95 134
96 135 // Count solved.
97 - // Before using $GLOBALS['bbp_solved_topic'], check if it is set
98 - if (isset($GLOBALS['bbp_solved_topic']) && $GLOBALS['bbp_solved_topic']->is_solved($topic_id)) {
136 + // Before using $GLOBALS['forumax_solve_topic'], check if it is set
137 + if (isset($GLOBALS['forumax_solve_topic']) && $GLOBALS['forumax_solve_topic']->is_solved($topic_id)) {
99 138 $count_solved++;
100 139 } else {
101 140 $count_unsolved++;
102 141 }
142 + endwhile;
143 + wp_reset_postdata();
103 144
104 - endwhile; wp_reset_postdata();
145 + // Count trashed topics without loading them.
146 + $trash_topic = new WP_Query( [
147 + 'post_parent' => $item,
148 + 'post_type' => bbp_get_topic_post_type(),
149 + 'posts_per_page' => 1,
150 + 'post_status' => [ 'trash' ],
151 + 'fields' => 'ids',
152 + 'update_post_meta_cache' => false,
153 + 'update_post_term_cache' => false,
154 + ] );
155 + $count_trash = (int) $trash_topic->found_posts;
156 + ?>
157 + <div class="easydocs-tab <?php echo esc_attr( $active ); ?>" id="tab-<?php echo esc_attr( $item ); ?>">
105 158
106 - $trash_topic = new WP_Query(
107 - [
108 - 'post_parent' => $item,
109 - 'post_type' => bbp_get_topic_post_type(),
110 - 'posts_per_page' => -1,
111 - 'post_status' => [ 'trash' ],
112 - ]
113 - );
114 -
115 - while ( $trash_topic->have_posts() ) :
116 - $trash_topic->the_post();
117 - $trash_topic_id = get_the_ID();
118 - // Count trash.
119 - if ( bbp_is_topic_trash( $trash_topic_id ) ) {
120 - $count_trash++;
121 - }
122 - endwhile; wp_reset_postdata();
123 - ?>
124 - <div class="easydocs-tab <?php echo esc_attr( $active ); ?>" id="tab-<?php echo esc_attr( $item ); ?>">
159 + <!-- Tab filters. -->
160 + <?php include __DIR__ . '/admin_ui/tab_filters.php'; ?>
125 161
126 - <!-- Tab filters. -->
127 - <?php include __DIR__ . '/admin_ui/tab_filters.php'; ?>
162 + <!-- Children topics. -->
163 + <?php include __DIR__ . '/admin_ui/topics.php'; ?>
128 164
129 - <!-- Children topics. -->
130 - <?php include __DIR__ . '/admin_ui/topics.php'; ?>
165 + <?php if ( $children->post_count > 0 ) : ?>
166 + <div class="controls bbpc-admin-pagination" style="display:none;">
167 + <div class="mixitup-control mixitup-page-list"></div>
168 + </div>
169 + <?php endif; ?>
131 170
132 - <a class="easydocs-btn easydocs-btn-outline-blue easydocs-btn-sm easydocs-btn-round button button-info section-doc" id="bbpc-topic" target="_blank" name="submit" href="<?php echo admin_url( 'admin.php' ); ?>/Create_Topic.php?bbp_parentID=<?php echo $item; ?>&is_bbp_section=">
133 - <?php esc_html_e( 'Add Topic', 'bbp-core' ); ?>
134 - </a>
135 - </div>
171 + <a class="easydocs-btn easydocs-btn-outline-blue easydocs-btn-sm easydocs-btn-round button button-info section-doc" id="bbpc-topic" target="_blank" name="submit" href="javascript:void(0)" bbp_forum_id="<?php echo esc_attr( $item ); ?>">
172 + <?php esc_html_e( 'Add Topic', 'forumax' ); ?>
173 + </a>
174 + </div>
136 175 <?php
137 - endforeach;
138 - endif;
139 - ?>
176 + endforeach;
177 + endif;
178 + ?>
179 + </div>
140 180 </div>
141 181 </div>
182 + </main>
183 + <?php
184 + else :
185 + ?>
186 + <div class="eazydocs-no-content">
187 + <img src="<?php echo esc_url( FORUMAX_IMG . 'icon/folder-open.png' ); ?>" alt="<?php esc_attr_e( 'Folder Open', 'forumax' ); ?>">
188 + <p class="big-p"> <?php esc_html_e( 'No forums found. Get started by creating a new forum or importing demo data.', 'forumax' ); ?> </p>
189 + <p class="forumax-empty-actions">
190 + <a href="javascript:void(0)" type="button" id="bbpc-forum" class="button button-primary ezd-btn btn-lg">
191 + <span class="dashicons dashicons-plus-alt2"></span>
192 + <?php esc_html_e( 'Create Forum', 'forumax' ); ?>
193 + </a>
194 + <span class="forumax-actions-separator"><?php esc_html_e( 'or', 'forumax' ); ?></span>
195 + <a href="javascript:void(0)" type="button" id="forumax-import-demo" class="button ezd-btn btn-lg forumax-import-demo-btn">
196 + <span class="dashicons dashicons-database-import"></span>
197 + <?php esc_html_e( 'Import Demo Data', 'forumax' ); ?>
198 + </a>
199 + </p>
142 200 </div>
143 - </main>
144 - <?php
145 - else :
201 + <?php
202 + endif;
146 203 ?>
147 - <div class="eazydocs-no-content">
148 - <img src="<?php echo BBPC_IMG; ?>/icon/folder-open.png" alt="<?php esc_attr_e( 'Folder Open', 'bbp-core' ); ?>">
149 - <p class="big-p"> <?php esc_html_e( 'No forum has been found . Perhaps', 'bbp-core' ); ?> </p>
150 - <p> <br>
151 - <a href="<?php echo admin_url( 'admin.php' ); ?>/Create_Forum.php?bbp_parent_title=" target="_blank" type="button" id="bbpc-forum" class="button button-primary ezd-btn btn-lg">
152 - <?php esc_html_e( 'Create Forum', 'bbp-core' ); ?>
153 - </a>
154 - </p>
155 - </div>
156 - <?php
157 - endif; //TODO: Fix open topics not being selected issue.
158 - ?>
204 + </div>
159 205 </div>
160 -</div>
161 206
162 207 <script>
163 208 (function ($) {
164 209 $(document).ready(function () {
165 210 let docContainer = document.querySelectorAll('.easydocs-tab');
166 -
167 211 var config = {
168 212 controls: {
169 213 scope: 'local',
170 214 },
@@ -171,15 +215,31 @@
171 215 animation: {
172 216 enable: false,
173 217 },
174 218 load: {
175 - filter: '<?php echo esc_js( $bbpc_opt['default_filter'] ?? '.open-topics' ); ?>'
219 + filter: '<?php echo esc_js( forumax_get_opt( 'default_filter', '.open-topics' ) ); ?>'
220 + },
221 + pagination: {
222 + limit: 10,
223 + },
224 + callbacks: {
225 + onMixEnd: function(state) {
226 + var pagination = state.container.querySelector('.bbpc-admin-pagination');
227 + if ( pagination ) {
228 + // Count matching items directly from the DOM using the active
229 + // filter selector. MixItUp always renders prev/next buttons so
230 + // children.length is unreliable for detecting multi-page results.
231 + var selector = state.activeFilter ? state.activeFilter.selector : '';
232 + var matchCount = ( ! selector || selector === 'all' )
233 + ? state.container.querySelectorAll('.mix').length
234 + : state.container.querySelectorAll(selector).length;
235 + pagination.style.display = matchCount > 10 ? 'block' : 'none';
236 + }
237 + }
176 238 }
177 239 };
178 -
179 240 for (let i = 0; i < docContainer.length; i++) {
180 241 var mixer1 = mixitup(docContainer[i], config);
181 242 }
182 243 });
183 244 })(jQuery);
184 -</script>
185 -
245 +</script>