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/admin/Menu.php +48 -367 2.4.22.2.0 View file →
@@ -1,423 +1,104 @@
1 1 <?php
2 -
3 2 namespace admin;
4 3
5 4 /**
6 - * Handle Forumax Admin Menu logic.
5 + * Menu class for Forumax admin pages.
6 + *
7 + * Handles the registration and rendering of all admin menu pages.
8 + *
9 + * @package Forumax
10 + * @since 1.0.0
7 11 */
8 12 class Menu {
9 13
10 - public function __construct() {
11 - add_filter( 'bbp_register_forum_post_type', [ $this, 'hide_bbp_cpt_top_level_menu' ] );
12 - add_filter( 'bbp_register_topic_post_type', [ $this, 'hide_bbp_cpt_top_level_menu' ] );
13 - add_filter( 'bbp_register_reply_post_type', [ $this, 'hide_bbp_cpt_top_level_menu' ] );
14 -
15 - // Use priority 99 to ensure we run after other plugins (like bbPress) to hide their menus if needed.
16 - add_action( 'admin_menu', [ $this, 'forumax_admin_menu' ], 99 );
17 - add_action( 'admin_menu', [ $this, 'reorder_forumax_submenu' ], 999 );
18 - add_filter( 'parent_file', [ $this, 'fix_parent_active_state' ] );
19 - add_filter( 'submenu_file', [ $this, 'fix_submenu_active_state' ] );
20 -
21 - // Enqueue admin assets specifically for our menu styling
22 - add_filter( 'admin_body_class', [ $this, 'add_admin_body_class' ] );
23 -
24 - // Setup wizard AJAX save handler
25 - add_action( 'wp_ajax_forumax_save_wizard_settings', [ $this, 'ajax_save_wizard_settings' ] );
26 - }
27 -
28 14 /**
29 - * Add a specific class to the admin body to target our sidebar styling reliably.
15 + * Constructor.
16 + *
17 + * Initializes the menu hooks.
30 18 */
31 - public function add_admin_body_class( $classes ) {
32 - $screen = get_current_screen();
33 - if ( $screen && ( 'forumax' === $screen->parent_base || strpos( $screen->id, 'forumax' ) !== false ) ) {
34 - $classes .= ' forumax-admin-page';
35 - }
36 - return $classes;
19 + public function __construct() {
20 + add_action( 'admin_menu', [ $this, 'forumax_admin_menu' ] );
37 21 }
38 22
39 23 /**
40 - * Hide bbPress CPT top-level menus so Forumax can add them explicitly.
24 + * Create Admin menu and submenu pages.
41 25 *
42 - * @param array $args Post type registration arguments.
26 + * Registers the main Forumax menu and all submenu pages.
27 + * The main menu opens the Dashboard page.
43 28 *
44 - * @return array
29 + * @return void
45 30 */
46 - public function hide_bbp_cpt_top_level_menu( $args ) {
47 - return $this->set_cpt_menu_parent( $args );
48 - }
49 -
50 - /**
51 - * Register the Forumax Admin Menu.
52 - */
53 31 public function forumax_admin_menu() {
54 32 $capability = 'manage_options';
55 - $show_classic_post_types = (bool) forumax_get_opt( 'is_bbp_post_types_hidden', false );
56 33
57 - // 1. Root Menu
34 + // Main menu page - opens Dashboard.
58 35 add_menu_page(
59 - esc_html__( 'Forumax', 'forumax' ),
60 - esc_html__( 'Forumax', 'forumax' ),
36 + __( 'Forumax', 'forumax' ),
37 + __( 'Forumax', 'forumax' ),
61 38 $capability,
62 39 'forumax',
63 40 [ $this, 'forumax_dashboard_page' ],
64 - 'dashicons-groups',
65 - 6
41 + FORUMAX_IMG . 'admin/favicon.png',
42 + 20
66 43 );
67 44
68 - // 2. Dashboard & Builder
45 + // Dashboard submenu (matches parent slug so it doesn't duplicate).
69 46 add_submenu_page(
70 47 'forumax',
71 - esc_html__( 'Dashboard', 'forumax' ),
72 - esc_html__( 'Dashboard', 'forumax' ),
48 + __( 'Dashboard', 'forumax' ),
49 + __( 'Dashboard', 'forumax' ),
73 50 $capability,
74 51 'forumax',
75 52 [ $this, 'forumax_dashboard_page' ]
76 53 );
77 54
55 + // Forum Builder submenu.
78 56 add_submenu_page(
79 57 'forumax',
80 - esc_html__( 'Forum Builder', 'forumax' ),
81 - esc_html__( 'Forum Builder', 'forumax' ),
58 + __( 'Forum Builder', 'forumax' ),
59 + __( 'Forum Builder', 'forumax' ),
82 60 $capability,
83 61 'forumax-builder',
84 62 [ $this, 'forumax_plugin_page' ]
85 63 );
86 64
87 - // CPT top-level entries are suppressed via the bbp_register_*_post_type filters
88 - // in __construct() (show_in_menu = false), so no removal call is needed here.
89 -
90 - if ( $show_classic_post_types ) {
91 - // --- SEPARATOR 1 (Before Content Management) ---
92 - $this->add_frmx_separator( '1' );
93 -
94 - // 3. Post Type utility links.
95 - $cpts = [
96 - 'forum' => esc_html__( 'Forums', 'forumax' ),
97 - 'topic' => esc_html__( 'Topics', 'forumax' ),
98 - 'reply' => esc_html__( 'Replies', 'forumax' ),
99 - ];
100 -
101 - foreach ( $cpts as $pt => $label ) {
102 - $post_type_object = get_post_type_object( $pt );
103 -
104 - if ( empty( $post_type_object ) ) {
105 - continue;
106 - }
107 -
108 - $edit_cap = $post_type_object->cap->edit_posts;
109 - $create_cap = isset( $post_type_object->cap->create_posts ) ? $post_type_object->cap->create_posts : $edit_cap;
110 -
111 - // Top item: the CPT list page (e.g. edit.php?post_type=forum).
112 - add_submenu_page(
113 - 'forumax',
114 - $label,
115 - $label,
116 - $edit_cap,
117 - "edit.php?post_type={$pt}"
118 - );
119 -
120 - // Indented child: Add New.
121 - add_submenu_page(
122 - 'forumax',
123 - esc_html__( 'Add New ', 'forumax' ) . $label,
124 - '&nbsp;&nbsp;&nbsp; ' . esc_html__( 'Add New', 'forumax' ),
125 - $create_cap,
126 - "post-new.php?post_type={$pt}&is_child=true"
127 - );
128 -
129 - // Indented children: taxonomy pages (e.g. topic tags).
130 - $taxs = get_object_taxonomies( $pt, 'objects' );
131 - foreach ( $taxs as $t_slug => $t_obj ) {
132 - if ( ! $t_obj->show_ui ) {
133 - continue;
134 - }
135 - add_submenu_page(
136 - 'forumax',
137 - esc_html( $t_obj->labels->name ),
138 - '&nbsp;&nbsp;&nbsp; ' . esc_html( $t_obj->labels->name ),
139 - $t_obj->cap->manage_terms,
140 - "edit-tags.php?taxonomy={$t_slug}&post_type={$pt}&is_child=true"
141 - );
142 - }
143 -
144 - // Separator after each CPT block except the last (Separator 2 closes the section).
145 - if ( 'reply' !== $pt ) {
146 - $this->add_frmx_separator( "after-{$pt}" );
147 - }
148 - }
149 -
150 - // --- SEPARATOR 2 (Before Utilities) ---
151 - $this->add_frmx_separator( '2' );
65 + // Remove default bbPress menu items if setting is disabled.
66 + if ( ! forumax_get_opt( 'is_bbp_post_types_hidden' ) ) {
67 + remove_menu_page( 'edit.php?post_type=forum' );
68 + remove_menu_page( 'edit.php?post_type=topic' );
69 + remove_menu_page( 'edit.php?post_type=reply' );
152 70 }
153 -
154 - // 4. Utility Pages
155 - add_submenu_page(
156 - 'forumax',
157 - esc_html__( 'Setup Wizard', 'forumax' ),
158 - esc_html__( 'Setup Wizard', 'forumax' ),
159 - $capability,
160 - 'forumax-setup',
161 - [ $this, 'forumax_setup_wizard_page' ]
162 - );
163 -
164 - remove_submenu_page( 'forumax', 'forumax-settings' );
165 -
166 - add_submenu_page(
167 - 'forumax',
168 - esc_html__( 'Settings', 'forumax' ),
169 - esc_html__( 'Settings', 'forumax' ),
170 - $capability,
171 - 'forumax-settings',
172 - [ $this, 'forumax_settings_page' ]
173 - );
174 -
175 - if ( ! $this->has_premium_analytics_page() ) {
176 - add_submenu_page(
177 - 'forumax',
178 - esc_html__( 'Analytics', 'forumax' ),
179 - esc_html__( 'Analytics', 'forumax' ),
180 - $capability,
181 - 'forumax-analytics',
182 - [ $this, 'forumax_analytics_page' ]
183 - );
184 - }
185 -
186 - // --- SEPARATOR 3 (Before Account Section added by others) ---
187 - $this->add_frmx_separator( '3' );
188 71 }
189 72
190 73 /**
191 - * Reorder the Forumax submenu to ensure Settings and others are correctly placed.
74 + * Forum Builder page callback function.
75 + *
76 + * Renders the main forum builder admin UI.
77 + *
78 + * @return void
192 79 */
193 - public function reorder_forumax_submenu() {
194 - global $submenu;
195 -
196 - if ( ! isset( $submenu['forumax'] ) || ! is_array( $submenu['forumax'] ) ) {
197 - return;
198 - }
199 -
200 - $current_menu = $submenu['forumax'];
201 -
202 - $weights = [
203 - 'forumax' => 1000,
204 - 'forumax-builder' => 2000,
205 - 'frmx-menu-sep-1' => 3000,
206 - ];
207 -
208 - $weighted_items = [];
209 -
210 - foreach ( $current_menu as $key => $item ) {
211 - $slug = isset( $item[2] ) ? $item[2] : '';
212 - $weight = 10000; // Default for "others"
213 -
214 - if ( isset( $weights[$slug] ) ) {
215 - $weight = $weights[$slug];
216 - } elseif ( strpos( $slug, 'post_type=forum' ) !== false ) {
217 - $weight = 4000;
218 - if ( strpos( $slug, 'post-new.php' ) !== false ) $weight = 4100;
219 - elseif ( strpos( $slug, 'edit-tags.php' ) !== false ) $weight = 4200;
220 - } elseif ( $slug === 'frmx-menu-sep-after-forum' ) {
221 - $weight = 4900;
222 - } elseif ( strpos( $slug, 'post_type=topic' ) !== false ) {
223 - $weight = 5000;
224 - if ( strpos( $slug, 'post-new.php' ) !== false ) $weight = 5100;
225 - elseif ( strpos( $slug, 'edit-tags.php' ) !== false ) $weight = 5200;
226 - } elseif ( $slug === 'frmx-menu-sep-after-topic' ) {
227 - $weight = 5900;
228 - } elseif ( strpos( $slug, 'post_type=reply' ) !== false ) {
229 - $weight = 6000;
230 - if ( strpos( $slug, 'post-new.php' ) !== false ) $weight = 6100;
231 - elseif ( strpos( $slug, 'edit-tags.php' ) !== false ) $weight = 6200;
232 - } else {
233 - // Other knowns
234 - if ( $slug === 'frmx-menu-sep-2' ) $weight = 7000;
235 - elseif ( $slug === 'forumax-setup' ) $weight = 8000;
236 - elseif ( $slug === 'forumax-settings' ) $weight = 9000;
237 - // others default to 10000
238 - elseif ( $slug === 'forumax-analytics' ) $weight = 11000;
239 - elseif ( $slug === 'frmx-menu-sep-3' ) $weight = 12000;
240 - }
241 -
242 - // Handle duplicate weights by incrementing
243 - while ( isset( $weighted_items[$weight] ) ) {
244 - $weight++;
245 - }
246 - $weighted_items[$weight] = $item;
247 - }
248 -
249 - ksort( $weighted_items );
250 - $submenu['forumax'] = $weighted_items;
80 + public function forumax_plugin_page() {
81 + include plugin_dir_path( __FILE__ ) . '/menu/admin_ui.php';
251 82 }
252 83
253 84 /**
254 - * Matches sidebar logic for parent menu expansion.
85 + * Dashboard page callback function.
86 + *
87 + * Renders the comprehensive dashboard with statistics and activity.
88 + *
89 + * @return void
255 90 */
256 - public function fix_parent_active_state( $parent_file ) {
257 - global $current_screen;
258 - $bbp_types = [ 'forum', 'topic', 'reply' ];
259 - if ( $current_screen && in_array( $current_screen->post_type, $bbp_types, true ) ) {
260 - return 'forumax';
261 - }
262 - return $parent_file;
263 - }
264 -
265 - /**
266 - * Matches sidebar logic for specific submenu highlighting.
267 - */
268 - public function fix_submenu_active_state( $submenu_file ) {
269 - global $current_screen, $pagenow;
270 - $bbp_types = [ 'forum', 'topic', 'reply' ];
271 - $post_type = $current_screen ? $current_screen->post_type : '';
272 -
273 - if ( in_array( $post_type, $bbp_types, true ) ) {
274 - if ( 'post-new.php' === $pagenow ) {
275 - return "post-new.php?post_type={$post_type}&is_child=true";
276 - }
277 - if ( $current_screen->taxonomy ) {
278 - return "edit-tags.php?taxonomy={$current_screen->taxonomy}&post_type={$post_type}&is_child=true";
279 - }
280 - return "edit.php?post_type={$post_type}";
281 - }
282 - return $submenu_file;
283 - }
284 -
285 - /**
286 - * Helpers & Callbacks
287 - */
288 - private function set_cpt_menu_parent( array $args ) : array {
289 - // Prevent WordPress from auto-generating a top-level or parented menu entry
290 - // for this CPT. Forumax adds explicit submenu links in forumax_admin_menu().
291 - $args['show_in_menu'] = false;
292 -
293 - return $args;
294 - }
295 -
296 - private function add_frmx_separator( $id ) {
297 - add_submenu_page(
298 - 'forumax',
299 - '',
300 - '<span class="frmx-menu-separator" aria-hidden="true"></span>',
301 - 'manage_options',
302 - 'frmx-menu-sep-' . $id
303 - );
304 - }
305 -
306 - public function forumax_plugin_page() {
307 - include plugin_dir_path( __FILE__ ) . '/menu/admin_ui.php';
308 - }
309 -
310 91 public function forumax_dashboard_page() {
311 92 include plugin_dir_path( __FILE__ ) . '/menu/dashboard.php';
312 93 }
313 94
314 - public function forumax_setup_wizard_page() {
315 - include plugin_dir_path( __FILE__ ) . '/menu/setup-wizard.php';
316 - }
317 -
318 95 /**
319 - * AJAX handler — save all wizard settings.
96 + * Legacy Dashboard Statistics.
320 97 *
321 - * Accepts POST data from the setup wizard JS, validates input,
322 - * and persists the values as WordPress options.
323 - *
324 - * Flow: nonce → capability → sanitize → save → respond.
98 + * @deprecated 2.1.0 Use forumax_dashboard_page() instead.
99 + * @return void
325 100 */
326 - public function ajax_save_wizard_settings() {
327 - if ( ! check_ajax_referer( 'forumax-wizard-nonce', 'nonce', false ) ) {
328 - wp_send_json_error( [ 'message' => __( 'Invalid security token.', 'forumax' ) ] );
329 - }
330 -
331 - if ( ! current_user_can( 'manage_options' ) ) {
332 - wp_send_json_error( [ 'message' => __( 'You do not have permission to save these settings.', 'forumax' ) ] );
333 - }
334 -
335 - // ── Sanitize & save each field ──────────────────────────────────
336 - $post = $_POST; // phpcs:ignore WordPress.Security.NonceVerification.Missing -- verified above
337 -
338 - // Text fields: forum title + description.
339 - $text_fields = [ 'create_forum', 'forum_description' ];
340 - foreach ( $text_fields as $field ) {
341 - if ( isset( $post[ $field ] ) ) {
342 - update_option(
343 - 'forumax_wizard_' . $field,
344 - 'forum_description' === $field
345 - ? sanitize_textarea_field( $post[ $field ] )
346 - : sanitize_text_field( $post[ $field ] ),
347 - false
348 - );
349 - }
350 - }
351 -
352 - // Boolean fields: auto-create pages.
353 - $bool_fields = [ 'create_pages' ];
354 - foreach ( $bool_fields as $field ) {
355 - update_option( 'forumax_wizard_' . $field, isset( $post[ $field ] ) && '1' === $post[ $field ] ? '1' : '0', false );
356 - }
357 -
358 - // Radio fields with allowlists.
359 - $allowed_setup_types = [ 'single', 'sample', 'skip' ];
360 - $allowed_layout_styles = [ 'classic', 'modern', 'card' ];
361 -
362 - if ( isset( $post['setup_type'] ) && in_array( $post['setup_type'], $allowed_setup_types, true ) ) {
363 - update_option( 'forumax_wizard_setup_type', sanitize_key( $post['setup_type'] ), false );
364 - }
365 -
366 - if ( isset( $post['layout_style'] ) && in_array( $post['layout_style'], $allowed_layout_styles, true ) ) {
367 - update_option( 'forumax_wizard_layout_style', sanitize_key( $post['layout_style'] ), false );
368 - }
369 -
370 - // Brand color — save to wizard option AND the real CSF settings for immediate effect.
371 - if ( ! empty( $post['brand_color'] ) ) {
372 - $brand_color = sanitize_hex_color( $post['brand_color'] );
373 - if ( $brand_color ) {
374 - update_option( 'forumax_wizard_brand_color', $brand_color, false );
375 -
376 - // Write directly to CSF option so the frontend picks it up immediately.
377 - $csf_options = get_option( 'bbp_core_settings', [] );
378 - if ( ! is_array( $csf_options ) ) {
379 - $csf_options = [];
380 - }
381 - $csf_options['bbpc_brand_color'] = $brand_color;
382 - update_option( 'bbp_core_settings', $csf_options );
383 - }
384 - }
385 -
386 - // Mark wizard as completed so it can be skipped on future visits.
387 - update_option( 'forumax_wizard_completed', '1', false );
388 -
389 - wp_send_json_success( [ 'message' => __( 'Settings saved.', 'forumax' ) ] );
390 - }
391 -
392 - public function forumax_settings_page() {
393 - if ( ! class_exists( '\CSF_Options' ) || ! class_exists( '\CSF' ) ) {
394 - wp_die( esc_html__( 'Forumax settings could not be loaded.', 'forumax' ) );
395 - }
396 -
397 - if ( empty( \CSF::$args['admin_options']['bbp_core_settings'] ) || empty( \CSF::$args['sections']['bbp_core_settings'] ) ) {
398 - wp_die( esc_html__( 'Forumax settings are not registered.', 'forumax' ) );
399 - }
400 -
401 - static $settings_renderer = null;
402 -
403 - if ( null === $settings_renderer ) {
404 - $settings_renderer = \CSF_Options::instance(
405 - 'bbp_core_settings',
406 - [
407 - 'args' => \CSF::$args['admin_options']['bbp_core_settings'],
408 - 'sections' => \CSF::$args['sections']['bbp_core_settings'],
409 - ]
410 - );
411 - }
412 -
413 - $settings_renderer->add_options_html();
414 - }
415 -
416 - public function forumax_analytics_page() {
417 - include plugin_dir_path( __FILE__ ) . '/menu/analytics.php';
418 - }
419 -
420 - private function has_premium_analytics_page() {
421 - return defined( 'BBPCPRO_PATH' ) || class_exists( '\Forumax_Pro\Admin\Analytics' );
101 + public function forumax_statistics_dashboard() {
102 + include plugin_dir_path( __FILE__ ) . '/menu/statistics.php';
422 103 }
423 104 }