PluginProbe
Forumax – AI Powered Advanced Community Forum Plugin / trunk
Forumax – AI Powered Advanced Community Forum Plugin vtrunk
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 / admin / Menu.php

Menu.php in Forumax – AI Powered Advanced Community Forum Plugin trunk, at includes/admin/Menu.php

424 lines 12.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace admin;
4
5 /**
6 * Handle Forumax Admin Menu logic.
7 */
8 class Menu {
9
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 /**
29 * Add a specific class to the admin body to target our sidebar styling reliably.
30 */
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;
37 }
38
39 /**
40 * Hide bbPress CPT top-level menus so Forumax can add them explicitly.
41 *
42 * @param array $args Post type registration arguments.
43 *
44 * @return array
45 */
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 public function forumax_admin_menu() {
54 $capability = 'manage_options';
55 $show_classic_post_types = (bool) forumax_get_opt( 'is_bbp_post_types_hidden', false );
56
57 // 1. Root Menu
58 add_menu_page(
59 esc_html__( 'Forumax', 'forumax' ),
60 esc_html__( 'Forumax', 'forumax' ),
61 $capability,
62 'forumax',
63 [ $this, 'forumax_dashboard_page' ],
64 'dashicons-groups',
65 6
66 );
67
68 // 2. Dashboard & Builder
69 add_submenu_page(
70 'forumax',
71 esc_html__( 'Dashboard', 'forumax' ),
72 esc_html__( 'Dashboard', 'forumax' ),
73 $capability,
74 'forumax',
75 [ $this, 'forumax_dashboard_page' ]
76 );
77
78 add_submenu_page(
79 'forumax',
80 esc_html__( 'Forum Builder', 'forumax' ),
81 esc_html__( 'Forum Builder', 'forumax' ),
82 $capability,
83 'forumax-builder',
84 [ $this, 'forumax_plugin_page' ]
85 );
86
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' );
152 }
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 }
189
190 /**
191 * Reorder the Forumax submenu to ensure Settings and others are correctly placed.
192 */
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;
251 }
252
253 /**
254 * Matches sidebar logic for parent menu expansion.
255 */
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 public function forumax_dashboard_page() {
311 include plugin_dir_path( __FILE__ ) . '/menu/dashboard.php';
312 }
313
314 public function forumax_setup_wizard_page() {
315 include plugin_dir_path( __FILE__ ) . '/menu/setup-wizard.php';
316 }
317
318 /**
319 * AJAX handler — save all wizard settings.
320 *
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.
325 */
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' );
422 }
423 }
424