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 +96 -35 1.4.0 → 2.5.1 View file →
@@ -2,8 +2,60 @@
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 61 ?>
@@ -57,15 +109,12 @@
57 109 $count_solved = 0;
58 110 $count_unsolved = 0;
59 111 $count_trash = 0;
60 112
113 + $reply_stats = forumax_admin_reply_stats( wp_list_pluck( $children->posts, 'ID' ) );
114 +
61 115 while ( $children->have_posts() ) : $children->the_post();
62 116 $topic_id = get_the_ID();
63 - $replies = get_children( [
64 - 'post_parent' => $topic_id,
65 - 'post_type' => bbp_get_reply_post_type(),
66 - 'post_status' => [ 'publish', 'draft', 'pending' ],
67 - ] );
68 117
69 118 // Count open/closed topics.
70 119 if ( bbp_is_topic_closed( $topic_id ) ) {
71 120 $count_closed++;
@@ -78,15 +127,15 @@
78 127 $count_hidden++;
79 128 }
80 129
81 130 // Replies count.
82 - if ( 0 == count( $replies ) ) {
131 + if ( empty( $reply_stats['total'][ $topic_id ] ) ) {
83 132 $count_no_reply++;
84 133 }
85 134
86 135 // Count solved.
87 - // Before using $GLOBALS['bbp_solved_topic'], check if it is set
88 - 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)) {
89 138 $count_solved++;
90 139 } else {
91 140 $count_unsolved++;
92 141 }
@@ -92,24 +141,19 @@
92 141 }
93 142 endwhile;
94 143 wp_reset_postdata();
95 144
145 + // Count trashed topics without loading them.
96 146 $trash_topic = new WP_Query( [
97 - 'post_parent' => $item,
98 - 'post_type' => bbp_get_topic_post_type(),
99 - 'posts_per_page' => -1,
100 - 'post_status' => [ 'trash' ],
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,
101 154 ] );
102 -
103 - while ( $trash_topic->have_posts() ) :
104 - $trash_topic->the_post();
105 - $trash_topic_id = get_the_ID();
106 - // Count trash.
107 - if ( bbp_is_topic_trash( $trash_topic_id ) ) {
108 - $count_trash++;
109 - }
110 - endwhile;
111 - wp_reset_postdata();
155 + $count_trash = (int) $trash_topic->found_posts;
112 156 ?>
113 157 <div class="easydocs-tab <?php echo esc_attr( $active ); ?>" id="tab-<?php echo esc_attr( $item ); ?>">
114 158
115 159 <!-- Tab filters. -->
@@ -117,20 +161,16 @@
117 161
118 162 <!-- Children topics. -->
119 163 <?php include __DIR__ . '/admin_ui/topics.php'; ?>
120 164
121 - <?php
122 - if ( $children->post_count > 10 ) :
123 - ?>
124 - <div class="controls bbpc-admin-pagination">
165 + <?php if ( $children->post_count > 0 ) : ?>
166 + <div class="controls bbpc-admin-pagination" style="display:none;">
125 167 <div class="mixitup-control mixitup-page-list"></div>
126 168 </div>
127 - <?php
128 - endif;
129 - ?>
169 + <?php endif; ?>
130 170
131 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 ); ?>">
132 - <?php esc_html_e( 'Add Topic', 'bbp-core' ); ?>
172 + <?php esc_html_e( 'Add Topic', 'forumax' ); ?>
133 173 </a>
134 174 </div>
135 175 <?php
136 176 endforeach;
@@ -143,14 +183,20 @@
143 183 <?php
144 184 else :
145 185 ?>
146 186 <div class="eazydocs-no-content">
147 - <img src="<?php echo esc_url( BBPC_IMG . 'icon/folder-open.png' ); ?>" alt="<?php esc_attr_e( 'Folder Open', 'bbp-core' ); ?>">
148 - <p class="big-p"> <?php esc_html_e( 'No forum has been found . Perhaps', 'bbp-core' ); ?> </p>
149 - <p> <br>
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">
150 190 <a href="javascript:void(0)" type="button" id="bbpc-forum" class="button button-primary ezd-btn btn-lg">
151 - <?php esc_html_e( 'Create Forum', 'bbp-core' ); ?>
191 + <span class="dashicons dashicons-plus-alt2"></span>
192 + <?php esc_html_e( 'Create Forum', 'forumax' ); ?>
152 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>
153 199 </p>
154 200 </div>
155 201 <?php
156 202 endif;
@@ -169,12 +215,27 @@
169 215 animation: {
170 216 enable: false,
171 217 },
172 218 load: {
173 - filter: '<?php echo esc_js( bbpc_get_opt( 'default_filter', '.open-topics' ) ); ?>'
219 + filter: '<?php echo esc_js( forumax_get_opt( 'default_filter', '.open-topics' ) ); ?>'
174 220 },
175 221 pagination: {
176 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 + }
177 238 }
178 239 };
179 240 for (let i = 0; i < docContainer.length; i++) {
180 241 var mixer1 = mixitup(docContainer[i], config);