PluginProbe ʕ •ᴥ•ʔ
Admin Help Docs / 2.0.0.1
Admin Help Docs v2.0.0.1
2.0.1.1 trunk 1.4.3.2 2.0.0 2.0.0.1 2.0.0.2 2.0.1
admin-help-docs / inc / tabs / admin-menu.php
admin-help-docs / inc / tabs Last commit date
css 3 months ago js 3 months ago admin-menu.php 3 months ago documentation.php 3 months ago faq.php 3 months ago import.php 3 months ago settings.php 3 months ago support.php 3 months ago
admin-menu.php
539 lines
1 <?php
2 /**
3 * Admin Menu Tab Loader
4 */
5
6 namespace PluginRx\AdminHelpDocs;
7
8 if ( ! defined( 'ABSPATH' ) ) exit;
9
10 class AdminMenu {
11
12 /**
13 * Returns all settings boxes.
14 */
15 public static function setting_boxes() : array {
16 $boxes = [
17 'menu' => __( 'Parent Menu Items', 'admin-help-docs' ),
18 'options' => __( 'Options', 'admin-help-docs' ),
19 ];
20
21 return apply_filters( 'helpdocs_settings_boxes', $boxes );
22 } // End setting_boxes()
23
24
25 /**
26 * Returns all settings fields.
27 */
28 public static function setting_fields( $defaults_only = false ) : array {
29 $fields = [
30 // Menu
31 [
32 'name' => 'admin_menu_order',
33 'label' => __( 'Parent Menu Items', 'admin-help-docs' ),
34 'type' => 'menu_order',
35 'box' => 'menu',
36 ],
37
38 // Options
39 [
40 'name' => 'enable_admin_menu_sorting',
41 'label' => __( 'Enable Admin Menu Sorting', 'admin-help-docs' ),
42 'desc' => __( 'This enables the drag-and-drop functionality for the admin menu.', 'admin-help-docs' ),
43 'type' => 'checkbox',
44 'sanitize' => 'sanitize_checkbox',
45 'box' => 'options',
46 'default' => false,
47 ],
48 [
49 'name' => 'show_menu_item_slugs',
50 'label' => __( 'Show Menu Item Slugs', 'admin-help-docs' ),
51 'desc' => __( 'Displays the slugs of menu items in the sorter.', 'admin-help-docs' ),
52 'type' => 'checkbox',
53 'sanitize' => 'sanitize_checkbox',
54 'box' => 'options',
55 'default' => false,
56 ],
57 [
58 'name' => 'colorize_separators',
59 'label' => __( 'Colorize Separators', 'admin-help-docs' ),
60 'desc' => __( 'Adds color to the separators in the admin menu.', 'admin-help-docs' ),
61 'type' => 'checkbox',
62 'sanitize' => 'sanitize_checkbox',
63 'box' => 'options',
64 'default' => false,
65 ],
66 [
67 'name' => 'color_admin_menu_sep',
68 'label' => __( 'Separator Color', 'admin-help-docs' ),
69 'type' => 'color',
70 'sanitize' => 'sanitize_text_field',
71 'box' => 'options',
72 'default' => '#D1D1D1',
73 ],
74 ];
75
76 // Only get the name, type and default for each field
77 if ( $defaults_only ) {
78 foreach ( $fields as $index => $field ) {
79 $fields[ $index ] = [
80 'name' => $field[ 'name' ],
81 'type' => $field[ 'type' ],
82 'default' => $field[ 'default' ] ?? null,
83 ];
84 }
85 }
86
87 return $fields;
88 } // End setting_fields()
89
90
91 /**
92 * The single instance of the class
93 *
94 * @var self|null
95 */
96 private static ?AdminMenu $instance = null;
97
98
99 /**
100 * Get the singleton instance
101 *
102 * @return self
103 */
104 public static function instance() : self {
105 return self::$instance ??= new self();
106 } // End instance()
107
108
109 /**
110 * Constructor
111 */
112 private function __construct() {
113 add_action( 'helpdocs_subheader_left', [ $this, 'render_save_reminder' ] );
114 add_action( 'admin_menu', [ $this, 'separators' ], PHP_INT_MAX );
115 add_filter( 'menu_order', [ $this, 'apply_menu_order' ], PHP_INT_MAX );
116 add_filter( 'custom_menu_order', [ $this, 'enable_custom_menu_order' ] );
117 add_filter( 'admin_body_class', [ $this, 'add_body_class' ] );
118 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_separator_styles' ] );
119 add_action( 'wp_ajax_helpdocs_save_menu_order', [ $this, 'ajax_save_settings' ] );
120 } // End __construct()
121
122
123 /**
124 * Render a reminder to save settings after changes are made
125 *
126 * @param string $current_tab The current active tab
127 */
128 public function render_save_reminder( $current_tab ) {
129 if ( $current_tab === 'admin-menu' ) {
130 echo '<span id="helpdocs-save-reminder">' . esc_html__( 'Remember to click "Save" after making changes to your settings.', 'admin-help-docs' ) . '</span>';
131 }
132 } // End render_save_reminder()
133
134
135 /**
136 * Render the tab
137 */
138 public function render_tab() {
139 $fields = $this->setting_fields();
140 $boxes = [];
141 $box_labels = $this->setting_boxes();
142
143 // Group fields by box
144 foreach ( $fields as $field ) {
145 $boxes[ $field[ 'box' ] ][] = $field;
146 }
147
148 echo '<div class="helpdocs-settings-grid">';
149 foreach ( $boxes as $box_id => $fields ) {
150 echo '<div class="helpdocs-settings-box">';
151 echo '<div class="helpdocs-settings-header">';
152 echo '<h2>' . esc_html( $box_labels[ $box_id ] ?? 'Unknown Box' ) . '</h2>';
153 echo '</div>';
154 echo '<div class="helpdocs-settings-body">';
155
156 $in_color_group = false;
157 foreach ( $fields as $index => $field ) {
158 $is_color = ( $field[ 'type' ] === 'color' );
159 $render_fn = "render_field_{$field[ 'type' ]}";
160
161 if ( $is_color && ! $in_color_group ) {
162 echo '<div class="helpdocs-color-grid">';
163 $in_color_group = true;
164 }
165
166 if ( ! $is_color && $in_color_group ) {
167 echo '</div>';
168 $in_color_group = false;
169 }
170
171 if ( method_exists( $this, $render_fn ) ) {
172 $this->{$render_fn}( $field );
173 } elseif ( method_exists( Settings::class, $render_fn ) ) {
174 Settings::instance()->{$render_fn}( $field );
175 }
176
177 $is_last = ( $index === array_key_last( $fields ) );
178
179 if ( $is_last && $in_color_group ) {
180 echo '</div>';
181 $in_color_group = false;
182 }
183 }
184 echo '</div></div>';
185 }
186 echo '</div>';
187 } // End render_tab()
188
189
190 /**
191 * Render the menu order field
192 *
193 * @param array $field The field definition array
194 */
195 private function render_field_menu_order( $field ) {
196 // Get the parent menu items
197 global $menu;
198
199 // Default WP separators
200 $default_separators = [
201 'separator1',
202 'separator2',
203 'separator-last'
204 ];
205
206 // Your extra separators
207 $extra_separators = [
208 'separator-helpdocs-extra1',
209 'separator-helpdocs-extra2',
210 'separator-helpdocs-extra3'
211 ];
212
213 // Keep only allowed separators
214 $allowed_separators = array_merge( $default_separators, $extra_separators );
215
216 // Build current menu items array
217 $admin_menu_items = [];
218 foreach ( $menu as $menu_item ) {
219 $slug = $menu_item[2] ?? '';
220
221 // Skip any separator not in allowed list
222 if ( str_starts_with( (string) $slug, 'separator' ) && ! in_array( $slug, $allowed_separators, true ) ) {
223 continue;
224 }
225
226 if ( str_starts_with( (string) $slug, 'separator' ) ) {
227 $label = '-- Separator -- (place at bottom to hide)';
228 } else {
229 $label = isset( $menu_item[0] ) ? wp_strip_all_tags( $menu_item[0] ) : '';
230 $label = preg_replace( '/\s+\d+.*$/', '', $label );
231 $label = trim( $label );
232 }
233
234 $admin_menu_items[ $slug ] = [
235 'label' => $label,
236 'sublabel' => $slug,
237 'value' => $slug
238 ];
239 }
240
241 // Ensure default WP separators are included
242 foreach ( $default_separators as $slug ) {
243 if ( ! isset( $admin_menu_items[ $slug ] ) ) {
244 $admin_menu_items[ $slug ] = [
245 'label' => '-- Separator -- (place at bottom to hide)',
246 'sublabel' => $slug,
247 'value' => $slug
248 ];
249 }
250 }
251
252 // Add your extra separators
253 foreach ( $extra_separators as $slug ) {
254 $admin_menu_items[ $slug ] = [
255 'label' => '-- Separator -- (place at bottom to hide)',
256 'sublabel' => $slug,
257 'value' => $slug
258 ];
259 }
260
261 // Apply saved order
262 $saved_order = get_option( 'helpdocs_admin_menu_order', [] );
263
264 if ( ! empty( $saved_order ) ) {
265 $ordered_items = [];
266 // First, add items in saved order if they exist
267 foreach ( $saved_order as $slug ) {
268 if ( isset( $admin_menu_items[ $slug ] ) ) {
269 $ordered_items[ $slug ] = $admin_menu_items[ $slug ];
270 }
271 }
272 // Then, add any new items not in saved order
273 foreach ( $admin_menu_items as $slug => $data ) {
274 if ( ! isset( $ordered_items[ $slug ] ) ) {
275 $ordered_items[ $slug ] = $data;
276 }
277 }
278 $admin_menu_items = $ordered_items;
279 }
280
281 $slug_display = get_option( 'helpdocs_show_menu_item_slugs', false ) ? 'block' : 'none';
282 ?>
283 <ul class="helpdocs-sorter">
284 <?php foreach ( $admin_menu_items as $data ) : ?>
285 <li class="helpdocs-sorter-item" draggable="true" data-value="<?php echo esc_attr( $data[ 'value' ] ); ?>">
286 <span class="dashicons dashicons-menu helpdocs-sort-handle"></span>
287 <span class="helpdocs-sort-text">
288 <span class="helpdocs-sort-label">
289 <?php echo esc_html( wp_strip_all_tags( $data[ 'label' ] ) ); ?>
290 </span>
291 </span>
292
293 <?php if ( ! empty( $data[ 'sublabel' ] ) ) : ?>
294 <code class="helpdocs-sort-sublabel" style="display: <?php echo esc_attr( $slug_display ); ?>;">
295 <?php echo esc_html( $data[ 'sublabel' ] ); ?>
296 </code>
297 <?php endif; ?>
298
299 <input type="hidden" name="admin_menu_order[]" value="<?php echo esc_attr( $data[ 'value' ] ); ?>">
300 </li>
301 <?php endforeach; ?>
302 </ul>
303 <?php
304 } // End render_field_menu_order()
305
306
307 /**
308 * Render a color picker field
309 *
310 * @param array $field The field definition array
311 */
312 public function render_field_color( $field ) {
313 $value = get_option( 'helpdocs_' . $field[ 'name' ], null );
314 if ( null === $value ) {
315 $value = $field[ 'default' ] ?? '';
316 }
317 $value = sanitize_text_field( $value );
318 ?>
319 <div id="helpdocs_field_<?php echo esc_attr( $field[ 'name' ] ); ?>" class="helpdocs-field color-picker-field">
320 <label for="<?php echo esc_attr( $field[ 'name' ] ); ?>">
321 <?php echo esc_html( $field[ 'label' ] ); ?>
322 </label>
323 <input type="color"
324 id="<?php echo esc_attr( $field[ 'name' ] ); ?>"
325 name="helpdocs_<?php echo esc_attr( $field[ 'name' ] ); ?>"
326 value="<?php echo esc_attr( $value ); ?>">
327 </div>
328 <?php
329 } // End render_field_color()
330
331
332 /**
333 * Enable custom menu order
334 */
335 public function enable_custom_menu_order( $enabled ) {
336 return filter_var( get_option( 'helpdocs_enable_admin_menu_sorting', false ), FILTER_VALIDATE_BOOLEAN ) ? true : $enabled;
337 } // End enable_custom_menu_order()
338
339
340 /**
341 * Add/remove separators on the admin menu based on saved order
342 */
343 public function separators() {
344 if ( $this->enable_custom_menu_order( false ) === false ) {
345 return;
346 }
347
348 global $menu;
349
350 $saved_order = get_option( 'helpdocs_admin_menu_order' );
351 if ( empty( $saved_order ) || ! is_array( $saved_order ) ) {
352 return;
353 }
354
355 $allowed = [
356 'separator1',
357 'separator2',
358 'separator-last',
359 'separator-helpdocs-extra1',
360 'separator-helpdocs-extra2',
361 'separator-helpdocs-extra3'
362 ];
363
364 foreach ( $saved_order as $slug ) {
365 if (
366 str_starts_with( $slug, 'separator' ) &&
367 in_array( $slug, $allowed, true ) &&
368 ! $this->separator_exists( $slug )
369 ) {
370 $menu[] = [
371 '',
372 'read',
373 $slug,
374 '',
375 'wp-menu-separator ' . $slug
376 ];
377 }
378 }
379 } // End separators()
380
381
382 /**
383 * Check if a separator with the given slug exists in the admin menu
384 */
385 private function separator_exists( $slug ) {
386 global $menu;
387 foreach ( $menu as $item ) {
388 if ( isset( $item[2] ) && $item[2] === $slug ) {
389 return true;
390 }
391 }
392 return false;
393 } // End separator_exists()
394
395
396 /**
397 * Apply the saved admin menu order
398 */
399 public function apply_menu_order( $menu_order ) {
400 if ( $this->enable_custom_menu_order( false ) === false ) {
401 return $menu_order;
402 }
403
404 $saved_order = get_option( 'helpdocs_admin_menu_order', [] );
405
406 if ( empty( $saved_order ) || ! is_array( $saved_order ) ) {
407 return $menu_order;
408 }
409
410 $final = [];
411 $seen = [];
412
413 // Add items in saved order if they exist in current menu
414 foreach ( $saved_order as $slug ) {
415 if ( in_array( $slug, $menu_order, true ) ) {
416 $final[] = $slug;
417 $seen[ $slug ] = true;
418 }
419 }
420
421 // Append any items not in saved order
422 foreach ( $menu_order as $slug ) {
423 if ( ! isset( $seen[ $slug ] ) ) {
424 $final[] = $slug;
425 }
426 }
427
428 return $final;
429 } // End apply_menu_order()
430
431
432 /**
433 * Add body class if separator coloring is enabled
434 */
435 public function add_body_class( $classes ) {
436 if ( get_option( 'helpdocs_colorize_separators' ) ) {
437 $classes .= ' helpdocs-separator-enabled';
438 }
439 return $classes;
440 } // End add_body_class()
441
442
443 /**
444 * Enqueue styles for colored separators
445 */
446 public function enqueue_separator_styles() {
447 $sep_color = get_option( 'helpdocs_color_admin_menu_sep', '#d1d1d1' );
448 $custom_css = "
449 #adminmenu div.wp-menu-separator.helpdocs-hidden-separator {
450 display: none;
451 }
452 .helpdocs-separator-enabled #adminmenu div.separator {
453 padding: 0;
454 border-top: 1px solid " . esc_html( $sep_color ) . ";
455 width: 90%;
456 margin: 8px auto;
457 height: 0;
458 opacity: 0.37;
459 background: transparent;
460 }
461 ";
462
463 wp_add_inline_style( 'wp-admin', $custom_css );
464 } // End enqueue_separator_styles()
465
466
467 /**
468 * Handle AJAX request to save settings
469 */
470 public function ajax_save_settings() {
471 check_ajax_referer( 'helpdocs_admin_menu_nonce', 'nonce' );
472 if ( ! current_user_can( Helpers::admin_role() ) ) {
473 wp_send_json_error( 'Insufficient permissions.' );
474 }
475
476 $fields = self::setting_fields();
477
478 $errors = [];
479
480 foreach ( $fields as $field ) {
481 $name = $field[ 'name' ];
482 $post_key = 'helpdocs_' . $name;
483
484 if ( $name === 'menu_order' ) {
485 continue;
486 }
487
488 if ( isset( $_POST[ 'settings' ][ $post_key ] ) ) {
489 // Sanitize below
490 $raw_value = wp_unslash( $_POST[ 'settings' ][ $post_key ] ); // phpcs:ignore
491 } else {
492 // If it's a checkbox and nothing is checked, default to 0
493 if ( $field[ 'type' ] === 'checkbox' ) {
494 $raw_value = 0;
495 } else {
496 continue;
497 }
498 }
499
500 // Determine type and sanitize accordingly
501 if ( $field[ 'type' ] === 'checkbox' ) {
502 $value = ( $raw_value == '1' ? '1' : '' );
503 } else {
504 $value = sanitize_text_field( $raw_value );
505 }
506
507 $updated = update_option( $post_key, $value );
508 if ( $updated === false && get_option( $post_key ) != $value ) {
509 $errors[] = $name;
510 }
511 }
512
513 $menu_order = wp_unslash( $_POST[ 'menu_order' ] ?? [] ); // phpcs:ignore
514 if ( is_array( $menu_order ) ) {
515 $menu_order = array_map( 'sanitize_text_field', $menu_order );
516 $updated = update_option( 'helpdocs_admin_menu_order', $menu_order );
517 if ( $updated === false && get_option( 'helpdocs_admin_menu_order' ) != $menu_order ) {
518 $errors[] = 'admin_menu_order';
519 }
520 }
521
522 if ( empty( $errors ) ) {
523 wp_send_json_success();
524 } else {
525 wp_send_json_error( 'Failed to save: ' . implode( ', ', $errors ) );
526 }
527 } // End ajax_save_settings()
528
529
530 /**
531 * Prevent cloning and unserializing
532 */
533 public function __clone() {}
534 public function __wakeup() {}
535
536 }
537
538
539 AdminMenu::instance();