__( 'Parent Menu Items', 'admin-help-docs' ), 'options' => __( 'Options', 'admin-help-docs' ), ]; return apply_filters( 'helpdocs_settings_boxes', $boxes ); } // End setting_boxes() /** * Returns all settings fields. */ public static function setting_fields( $defaults_only = false ) : array { $fields = [ // Menu [ 'name' => 'admin_menu_order', 'label' => __( 'Parent Menu Items', 'admin-help-docs' ), 'type' => 'menu_order', 'box' => 'menu', ], // Options [ 'name' => 'enable_admin_menu_sorting', 'label' => __( 'Enable Admin Menu Sorting', 'admin-help-docs' ), 'desc' => __( 'This enables the drag-and-drop functionality for the admin menu.', 'admin-help-docs' ), 'type' => 'checkbox', 'sanitize' => 'sanitize_checkbox', 'box' => 'options', 'default' => false, ], [ 'name' => 'show_menu_item_slugs', 'label' => __( 'Show Menu Item Slugs', 'admin-help-docs' ), 'desc' => __( 'Displays the slugs of menu items in the sorter.', 'admin-help-docs' ), 'type' => 'checkbox', 'sanitize' => 'sanitize_checkbox', 'box' => 'options', 'default' => false, ], [ 'name' => 'colorize_separators', 'label' => __( 'Colorize Separators', 'admin-help-docs' ), 'desc' => __( 'Adds color to the separators in the admin menu.', 'admin-help-docs' ), 'type' => 'checkbox', 'sanitize' => 'sanitize_checkbox', 'box' => 'options', 'default' => false, ], [ 'name' => 'color_admin_menu_sep', 'label' => __( 'Separator Color', 'admin-help-docs' ), 'type' => 'color', 'sanitize' => 'sanitize_text_field', 'box' => 'options', 'default' => '#D1D1D1', ], ]; // Only get the name, type and default for each field if ( $defaults_only ) { foreach ( $fields as $index => $field ) { $fields[ $index ] = [ 'name' => $field[ 'name' ], 'type' => $field[ 'type' ], 'default' => $field[ 'default' ] ?? null, ]; } } return $fields; } // End setting_fields() /** * The single instance of the class * * @var self|null */ private static ?AdminMenu $instance = null; /** * Get the singleton instance * * @return self */ public static function instance() : self { return self::$instance ??= new self(); } // End instance() /** * Constructor */ private function __construct() { add_action( 'helpdocs_subheader_left', [ $this, 'render_save_reminder' ] ); add_action( 'admin_menu', [ $this, 'separators' ], PHP_INT_MAX ); add_filter( 'menu_order', [ $this, 'apply_menu_order' ], PHP_INT_MAX ); add_filter( 'custom_menu_order', [ $this, 'enable_custom_menu_order' ] ); add_filter( 'admin_body_class', [ $this, 'add_body_class' ] ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_separator_styles' ] ); add_action( 'wp_ajax_helpdocs_save_menu_order', [ $this, 'ajax_save_settings' ] ); } // End __construct() /** * Render a reminder to save settings after changes are made * * @param string $current_tab The current active tab */ public function render_save_reminder( $current_tab ) { if ( $current_tab === 'admin-menu' ) { echo '' . esc_html__( 'Remember to click "Save" after making changes to your settings.', 'admin-help-docs' ) . ''; } } // End render_save_reminder() /** * Render the tab */ public function render_tab() { $fields = $this->setting_fields(); $boxes = []; $box_labels = $this->setting_boxes(); // Group fields by box foreach ( $fields as $field ) { $boxes[ $field[ 'box' ] ][] = $field; } echo '
'; foreach ( $boxes as $box_id => $fields ) { echo '
'; echo '
'; echo '

' . esc_html( $box_labels[ $box_id ] ?? 'Unknown Box' ) . '

'; echo '
'; echo '
'; $in_color_group = false; foreach ( $fields as $index => $field ) { $is_color = ( $field[ 'type' ] === 'color' ); $render_fn = "render_field_{$field[ 'type' ]}"; if ( $is_color && ! $in_color_group ) { echo '
'; $in_color_group = true; } if ( ! $is_color && $in_color_group ) { echo '
'; $in_color_group = false; } if ( method_exists( $this, $render_fn ) ) { $this->{$render_fn}( $field ); } elseif ( method_exists( Settings::class, $render_fn ) ) { Settings::instance()->{$render_fn}( $field ); } $is_last = ( $index === array_key_last( $fields ) ); if ( $is_last && $in_color_group ) { echo '
'; $in_color_group = false; } } echo '
'; } echo ''; } // End render_tab() /** * Render the menu order field * * @param array $field The field definition array */ private function render_field_menu_order( $field ) { // Get the parent menu items global $menu; // Default WP separators $default_separators = [ 'separator1', 'separator2', 'separator-last' ]; // Your extra separators $extra_separators = [ 'separator-helpdocs-extra1', 'separator-helpdocs-extra2', 'separator-helpdocs-extra3' ]; // Keep only allowed separators $allowed_separators = array_merge( $default_separators, $extra_separators ); // Build current menu items array $admin_menu_items = []; foreach ( $menu as $menu_item ) { $slug = $menu_item[2] ?? ''; // Skip any separator not in allowed list if ( str_starts_with( (string) $slug, 'separator' ) && ! in_array( $slug, $allowed_separators, true ) ) { continue; } if ( str_starts_with( (string) $slug, 'separator' ) ) { $label = '-- Separator -- (place at bottom to hide)'; } else { $label = isset( $menu_item[0] ) ? wp_strip_all_tags( $menu_item[0] ) : ''; $label = preg_replace( '/\s+\d+.*$/', '', $label ); $label = trim( $label ); } $admin_menu_items[ $slug ] = [ 'label' => $label, 'sublabel' => $slug, 'value' => $slug ]; } // Ensure default WP separators are included foreach ( $default_separators as $slug ) { if ( ! isset( $admin_menu_items[ $slug ] ) ) { $admin_menu_items[ $slug ] = [ 'label' => '-- Separator -- (place at bottom to hide)', 'sublabel' => $slug, 'value' => $slug ]; } } // Add your extra separators foreach ( $extra_separators as $slug ) { $admin_menu_items[ $slug ] = [ 'label' => '-- Separator -- (place at bottom to hide)', 'sublabel' => $slug, 'value' => $slug ]; } // Apply saved order $saved_order = get_option( 'helpdocs_admin_menu_order', [] ); if ( ! empty( $saved_order ) ) { $ordered_items = []; // First, add items in saved order if they exist foreach ( $saved_order as $slug ) { if ( isset( $admin_menu_items[ $slug ] ) ) { $ordered_items[ $slug ] = $admin_menu_items[ $slug ]; } } // Then, add any new items not in saved order foreach ( $admin_menu_items as $slug => $data ) { if ( ! isset( $ordered_items[ $slug ] ) ) { $ordered_items[ $slug ] = $data; } } $admin_menu_items = $ordered_items; } $slug_display = get_option( 'helpdocs_show_menu_item_slugs', false ) ? 'block' : 'none'; ?>
enable_custom_menu_order( false ) === false ) { return; } global $menu; $saved_order = get_option( 'helpdocs_admin_menu_order' ); if ( empty( $saved_order ) || ! is_array( $saved_order ) ) { return; } $allowed = [ 'separator1', 'separator2', 'separator-last', 'separator-helpdocs-extra1', 'separator-helpdocs-extra2', 'separator-helpdocs-extra3' ]; foreach ( $saved_order as $slug ) { if ( str_starts_with( $slug, 'separator' ) && in_array( $slug, $allowed, true ) && ! $this->separator_exists( $slug ) ) { $menu[] = [ '', 'read', $slug, '', 'wp-menu-separator ' . $slug ]; } } } // End separators() /** * Check if a separator with the given slug exists in the admin menu */ private function separator_exists( $slug ) { global $menu; foreach ( $menu as $item ) { if ( isset( $item[2] ) && $item[2] === $slug ) { return true; } } return false; } // End separator_exists() /** * Apply the saved admin menu order */ public function apply_menu_order( $menu_order ) { if ( $this->enable_custom_menu_order( false ) === false ) { return $menu_order; } $saved_order = get_option( 'helpdocs_admin_menu_order', [] ); if ( empty( $saved_order ) || ! is_array( $saved_order ) ) { return $menu_order; } $final = []; $seen = []; // Add items in saved order if they exist in current menu foreach ( $saved_order as $slug ) { if ( in_array( $slug, $menu_order, true ) ) { $final[] = $slug; $seen[ $slug ] = true; } } // Append any items not in saved order foreach ( $menu_order as $slug ) { if ( ! isset( $seen[ $slug ] ) ) { $final[] = $slug; } } return $final; } // End apply_menu_order() /** * Add body class if separator coloring is enabled */ public function add_body_class( $classes ) { if ( get_option( 'helpdocs_colorize_separators' ) ) { $classes .= ' helpdocs-separator-enabled'; } return $classes; } // End add_body_class() /** * Enqueue styles for colored separators */ public function enqueue_separator_styles() { $sep_color = get_option( 'helpdocs_color_admin_menu_sep', '#d1d1d1' ); $custom_css = " #adminmenu div.wp-menu-separator.helpdocs-hidden-separator { display: none; } .helpdocs-separator-enabled #adminmenu div.separator { padding: 0; border-top: 1px solid " . esc_html( $sep_color ) . "; width: 90%; margin: 8px auto; height: 0; opacity: 0.37; background: transparent; } "; wp_add_inline_style( 'wp-admin', $custom_css ); } // End enqueue_separator_styles() /** * Handle AJAX request to save settings */ public function ajax_save_settings() { check_ajax_referer( 'helpdocs_admin_menu_nonce', 'nonce' ); if ( ! current_user_can( Helpers::admin_role() ) ) { wp_send_json_error( 'Insufficient permissions.' ); } $fields = self::setting_fields(); $errors = []; foreach ( $fields as $field ) { $name = $field[ 'name' ]; $post_key = 'helpdocs_' . $name; if ( $name === 'menu_order' ) { continue; } if ( isset( $_POST[ 'settings' ][ $post_key ] ) ) { // Sanitize below $raw_value = wp_unslash( $_POST[ 'settings' ][ $post_key ] ); // phpcs:ignore } else { // If it's a checkbox and nothing is checked, default to 0 if ( $field[ 'type' ] === 'checkbox' ) { $raw_value = 0; } else { continue; } } // Determine type and sanitize accordingly if ( $field[ 'type' ] === 'checkbox' ) { $value = ( $raw_value == '1' ? '1' : '' ); } else { $value = sanitize_text_field( $raw_value ); } $updated = update_option( $post_key, $value ); if ( $updated === false && get_option( $post_key ) != $value ) { $errors[] = $name; } } $menu_order = wp_unslash( $_POST[ 'menu_order' ] ?? [] ); // phpcs:ignore if ( is_array( $menu_order ) ) { $menu_order = array_map( 'sanitize_text_field', $menu_order ); $updated = update_option( 'helpdocs_admin_menu_order', $menu_order ); if ( $updated === false && get_option( 'helpdocs_admin_menu_order' ) != $menu_order ) { $errors[] = 'admin_menu_order'; } } if ( empty( $errors ) ) { wp_send_json_success(); } else { wp_send_json_error( 'Failed to save: ' . implode( ', ', $errors ) ); } } // End ajax_save_settings() /** * Prevent cloning and unserializing */ public function __clone() {} public function __wakeup() {} } AdminMenu::instance();