'0', 'admbud_core_remove_help' => '0', 'admbud_core_remove_screen_options' => '0', 'admbud_notices_suppress' => '0', 'admbud_core_custom_footer_enabled' => '0', 'admbud_core_custom_footer_text' => '', 'admbud_wl_sidebar_logo_url' => '', 'admbud_wl_sidebar_logo_width' => 84, 'admbud_wl_sidebar_logo_height' => 0, 'admbud_wl_sidebar_width' => 160, 'admbud_wl_favicon_id' => '', 'admbud_wl_agency_name' => '', 'admbud_wl_agency_url' => '', 'admbud_wl_greeting' => '', 'admbud_wl_remove_wp_links' => '0', 'admbud_wl_footer_version' => '', 'admbud_wl_footer_quote' => '0', 'admbud_wl_hide_wp_news' => '0', // White Label - Cleanup (dissolved - items moved to Quick Settings) // White Label - Updates 'admbud_wl_hide_core_update' => '0', 'admbud_wl_hide_plugin_update' => '0', 'admbud_wl_hide_theme_update' => '0', 'admbud_wl_hide_php_nag' => '0', 'admbud_wl_update_policy' => '', 'admbud_wl_disable_update_emails' => '0', 'admbud_wl_disable_all_updates' => '0', // White Label - Gutenberg // -- Admin Bar -- 'admbud_show_in_adminbar' => '0', // -- Dashboard -- 'admbud_dashboard_role_pages' => '{}', 'admbud_dashboard_keep_widgets' => '', 'admbud_dashboard_custom_widgets' => '[]', // -- Modules -- 'admbud_modules_enabled_tabs' => '', // -- Source -- 'admbud_source_enabled' => '0', 'admbud_source_modules' => '', 'admbud_source_whitelist' => '[]', 'admbud_source_log' => '[]', 'admbud_receiver_sources' => '[]', // -- Activity Log -- 'admbud_activity_log_retention' => 90, 'admbud_activity_log_per_page' => 20, // -- Colours (defaults derived from Colours::DEFAULT_PRESET_SLUG; // today: 'violety'. To swap AB's default palette, update that // constant AND the values below — they must match the same // preset's `values` array in self::get_colour_presets().) -- 'admbud_colours_primary' => Colours::DEFAULT_PRIMARY, 'admbud_colours_secondary' => Colours::DEFAULT_SECONDARY, 'admbud_colours_menu_text' => Colours::DEFAULT_MENU_TEXT, 'admbud_colours_menu_bg' => Colours::DEFAULT_MENU_BG, 'admbud_colours_submenu_bg' => Colours::DEFAULT_SUBMENU_BG, 'admbud_colours_active_text' => '#ffffff', 'admbud_colours_sep_color' => '#5600ed', 'admbud_colours_body_bg' => '#ffffff', 'admbud_colours_shadow_colour' => '', 'admbud_colours_menu_item_sep' => '1', 'admbud_colours_menu_bg_opacity' => '100', 'admbud_colours_menu_text_opacity' => '100', 'admbud_colours_sidebar_gradient' => '1', 'admbud_colours_sidebar_grad_dir' => 'to top', 'admbud_colours_sidebar_grad_from' => '#2e1065', 'admbud_colours_sidebar_grad_to' => '#1e1b2e', 'admbud_colours_submenu_text' => '#ede9fe', 'admbud_colours_hover_text' => '#ede9fe', 'admbud_colours_hover_bg' => '', 'admbud_colours_active_bg' => '', 'admbud_colours_active_parent_text' => '#ffffff', 'admbud_colours_submenu_hover_bg' => Colours::DEFAULT_PRIMARY, 'admbud_colours_submenu_hover_text' => '#ede9fe', 'admbud_colours_submenu_active_bg' => '', 'admbud_colours_submenu_active_text' => '', 'admbud_colours_adminbar_bg' => '#1a1828', 'admbud_colours_adminbar_text' => '#d4d1e0', 'admbud_colours_adminbar_hover_bg' => Colours::DEFAULT_PRIMARY, 'admbud_colours_adminbar_submenu_bg' => '#1a1828', 'admbud_colours_adminbar_hover_text' => '#ede9fe', 'admbud_colours_adminbar_sub_text' => '#d4d1e0', 'admbud_colours_adminbar_sub_hover_bg' => '', 'admbud_colours_adminbar_sub_hover_text' => '#ffffff', 'admbud_colours_pill_maintenance' => '#dd3333', 'admbud_colours_pill_coming_soon' => '#dd3333', 'admbud_colours_pill_noindex' => '#dd9933', // Admin-bar pills: status indicators with meaningful semantic colours // (red=blocking, orange=warning). pill_admin_buddy is the AB brand // marker — neutral slate so a Colours-module-off site doesn't leak // AB's marketing purple onto every admin page's top bar. 'admbud_colours_pill_admin_buddy' => '#475569', // Slate 600 — neutral. // Content area tokens 'admbud_colours_content_heading' => '#1d2327', 'admbud_colours_content_text' => '#3c434a', 'admbud_colours_content_link' => Colours::DEFAULT_PRIMARY, 'admbud_colours_content_link_hover' => Colours::DEFAULT_SECONDARY, 'admbud_colours_table_header_bg' => '#f4effd', 'admbud_colours_table_header_text' => '#1d2327', 'admbud_colours_table_header_link' => '#1d2327', 'admbud_colours_table_row_bg' => '#ffffff', 'admbud_colours_table_row_text' => '#3c434a', 'admbud_colours_table_row_alt_bg' => '#f9f7fe', 'admbud_colours_table_row_alt_text' => '#3c434a', 'admbud_colours_table_row_hover' => '#efe7fc', 'admbud_colours_table_border' => '#decdfa', 'admbud_colours_table_row_separator' => '#ebe1fc', 'admbud_colours_table_action_link' => Colours::DEFAULT_PRIMARY, 'admbud_colours_table_title_link' => Colours::DEFAULT_PRIMARY, 'admbud_colours_input_bg' => '#ffffff', 'admbud_colours_input_border' => '#d1baf8', 'admbud_colours_input_focus' => Colours::DEFAULT_PRIMARY, 'admbud_colours_btn_primary_bg' => Colours::DEFAULT_PRIMARY, 'admbud_colours_btn_primary_text' => '#ffffff', 'admbud_colours_btn_primary_hover' => Colours::DEFAULT_SECONDARY, 'admbud_colours_btn_secondary_bg' => '#f7f3fd', 'admbud_colours_postbox_bg' => '#ffffff', 'admbud_colours_postbox_header_bg' => '#f9f7fe', 'admbud_colours_postbox_border' => '#decdfa', 'admbud_colours_postbox_text' => '', 'admbud_colours_notice_bg' => '#fbf9fe', 'admbud_colours_ui_radius' => 'off', 'admbud_css_exclusions' => '', // -- Login / Maintenance / Coming Soon front-end pages -- // // These render on public-facing pages (login screen, locked-site // maintenance + coming-soon pages), not in the admin chrome — they // must NOT default to AB's brand palette. The violet defaults // previously inherited from Colours::DEFAULT_PAGE_* leaked AB's // marketing identity into every visitor's first view. Defaults // are now neutral white/dark-slate, professional and brand-neutral. // Users who want the violet "Violety" preset still get it by // clicking the preset card under Colours → Presets. // // -- Login -- 'admbud_login_logo_url' => '', 'admbud_login_logo_width' => 84, 'admbud_login_logo_height' => 0, 'admbud_login_card_position' => 'center', 'admbud_login_bg_type' => 'solid', 'admbud_login_bg_color' => '#f1f1f1', // WP core's default wp-login bg. 'admbud_login_grad_from' => '#ffffff', 'admbud_login_grad_to' => '#f1f1f1', 'admbud_login_grad_direction' => 'to bottom right', 'admbud_login_bg_image_url' => '', 'admbud_login_bg_overlay_color' => '#000000', 'admbud_login_bg_overlay_opacity' => 30, 'admbud_login_card_bg_color' => '#ffffff', 'admbud_login_card_text_auto' => '1', 'admbud_login_card_text_color' => '#3c434a', 'admbud_login_card_width' => 400, 'admbud_login_btn_auto' => '1', 'admbud_login_btn_bg' => '#2271b1', // -- Maintenance -- 'admbud_maintenance_mode' => 'off', 'admbud_coming_soon_title' => 'Coming Soon', 'admbud_coming_soon_message' => "We're working on something exciting. Stay tuned!", 'admbud_maintenance_title' => 'Under Maintenance', 'admbud_maintenance_message' => "We're performing scheduled maintenance. We'll be back shortly!", 'admbud_maintenance_bypass_urls' => '', 'admbud_cs_bg_type' => 'solid', 'admbud_cs_bg_color' => '#ffffff', 'admbud_cs_grad_from' => '#ffffff', 'admbud_cs_grad_to' => '#f1f5f9', 'admbud_cs_grad_direction' => 'to bottom right', 'admbud_cs_bg_image_url' => '', 'admbud_cs_bg_overlay_color' => '#000000', 'admbud_cs_bg_overlay_opacity' => 30, 'admbud_cs_text_color' => '#1e293b', 'admbud_cs_message_color' => '#64748b', 'admbud_maint_bg_type' => 'solid', 'admbud_maint_bg_color' => '#ffffff', 'admbud_maint_grad_from' => '#ffffff', 'admbud_maint_grad_to' => '#f1f5f9', 'admbud_maint_grad_direction' => 'to bottom right', 'admbud_maint_bg_image_url' => '', 'admbud_maint_bg_overlay_color' => '#000000', 'admbud_maint_bg_overlay_opacity' => 30, 'admbud_maint_text_color' => '#1e293b', 'admbud_maint_message_color' => '#64748b', // -- SMTP -- 'admbud_smtp_enabled' => '0', 'admbud_smtp_mailer' => 'smtp', 'admbud_smtp_host' => '', 'admbud_smtp_port' => 587, 'admbud_smtp_username' => '', 'admbud_smtp_encryption' => 'tls', 'admbud_smtp_auth' => '1', 'admbud_smtp_from_name' => '', 'admbud_smtp_from_email' => '', 'admbud_smtp_fallback' => '0', 'admbud_smtp_disable_ssl_verify' => '0', 'admbud_smtp_preset' => 'custom', // -- Menu Customiser -- 'admbud_menu_config' => '{}', 'admbud_menu_show_item_borders' => '0', // -- Bricks -- 'admbud_bricks_enabled' => '0', 'admbud_bricks_color_mapping_enabled' => '1', 'admbud_bricks_custom_logo_url' => '', 'admbud_bricks_custom_spinner_url' => '', // -- Bricks BEM Generator -- 'admbud_bricks_bem_enabled' => '0', 'admbud_bricks_bem_auto_sync_labels' => '1', 'admbud_bricks_bem_show_modifiers' => '0', 'admbud_bricks_bem_default_action' => 'rename', 'admbud_bricks_bem_blacklist_extra' => '', // -- Bricks Quick Insert -- 'admbud_bricks_quick_enabled' => '0', 'admbud_bricks_quick_favourites' => '["section","container","heading","text-basic","image","button","icon","list"]', // -- Option Pages -- 'admbud_option_pages' => '{}', // -- Collections -- 'admbud_collections' => '{}', // -- Media Manager prefs (Pro) -- 'admbud_mm_default_color' => '', 'admbud_mm_default_expanded' => '1', 'admbud_mm_trash_enabled' => '1', 'admbud_mm_show_count' => '1', 'admbud_mm_show_size' => '1', 'admbud_mm_recursive_view' => '0', 'admbud_mm_tool_roles' => [], ]; } // -- Multisite notice ------------------------------------------------------ /** * Warn administrators on multisite installs that Admin Buddy does not * support network-synced settings. Shows once per admin, dismissible. */ // -- Menu ------------------------------------------------------------------ public function register_page(): void { // Menu icon: AB lettermark v3 - base64 data URI with fill="white" for WP menu rendering. $icon_svg = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCA4MCA4MCIgZmlsbD0ibm9uZSI+PHBhdGggZD0iTTcyLjY2NzMgNi44MDM4N0M3Ny4wNTg4IDExLjE5NCA3OS40NzEzIDE3LjAyMjEgNzkuNDcxMyAyMy4yMjg0Qzc5LjQ3MDYgMzAuOTE3NSA3NC44MjM4IDQ2LjMyOCA1Ni4yNDE3IDQ2LjQ1OEw0Ni40NTcyIDQ2LjU2NDVMNDYuNDU3MiAzMy40NzI0QzUxLjgyNzMgMzMuNTAwOSA2NS4yMjc1IDMxLjUzNjggNjYuMDAwNCAyMy4yMjg3QzY2LjAwMDQgMjAuNjI2NCA2NC45OTc1IDE4LjE2MDMgNjMuMTUzOSAxNi4yOTA1QzYxLjMxMTEgMTQuNDQ3MSA1OC44NDUxIDEzLjQ0NDIgNTYuMjE1NCAxMy40NDQyQzUxLjAxMTcgMTMuNDQ0MiA0Ni40NTcyIDE3Ljk5ODIgNDYuNDU3MiAyMy4yMjg3TDQ2LjQ1NzIgMzMuNDcyNEwzMy4wMTg4IDMzLjVMMzMuMDE0IDIzLjA5MzZDMzMuMDE0IDE3LjAyMjEgMzUuNDI2OCAxMS4xOTQyIDM5LjgxNzQgNi44MDM4N0M0NC4yMDgzIDIuNDEyNCA1MC4wMzU5IC02LjQxMzUzZS0wNSA1Ni4yNDI1IC02LjQxMzUzZS0wNUM2Mi40NDkxIC02LjQxMzUzZS0wNSA2OC4yNzcgMi40MTI0IDcyLjY2NzMgNi44MDM4N1oiIGZpbGw9IndoaXRlIi8+PHBhdGggZD0iTTcyLjY2NzMgNzMuMTk2MUM3Ny4wNTg4IDY4LjgwNiA3OS40NzEzIDYyLjk3NzkgNzkuNDcxMyA1Ni43NzE2Qzc5LjQ3MDYgNDkuMDgyNSA3NC44MjM4IDMzLjY3MiA1Ni4yNDE3IDMzLjU0Mkw0Ni40NTcyIDMzLjQzNTVMNDYuNDU3MiA0Ni41Mjc2QzUxLjgyNzMgNDYuNDk5MSA2NS4yMjc1IDQ4LjQ2MzIgNjYuMDAwNCA1Ni43NzEzQzY2LjAwMDQgNTkuMzczNiA2NC45OTc1IDYxLjgzOTcgNjMuMTUzOSA2My43MDk1QzYxLjMxMTEgNjUuNTUyOSA1OC44NDUxIDY2LjU1NTggNTYuMjE1NCA2Ni41NTU4QzUxLjAxMTcgNjYuNTU1OCA0Ni40NTcyIDYyLjAwMTggNDYuNDU3MiA1Ni43NzEzTDQ2LjQ1NzIgNDYuNTI3NkwzMy4wMTg4IDQ2LjVMMzMuMDE0IDU2LjkwNjRDMzMuMDE0IDYyLjk3NzkgMzUuNDI2OCA2OC44MDU4IDM5LjgxNzQgNzMuMTk2MUM0NC4yMDgzIDc3LjU4NzYgNTAuMDM1OSA4MC4wMDAxIDU2LjI0MjUgODAuMDAwMUM2Mi40NDkxIDgwLjAwMDEgNjguMjc3IDc3LjU4NzYgNzIuNjY3MyA3My4xOTYxWiIgZmlsbD0id2hpdGUiLz48cGF0aCBkPSJNNi44MDM4OCA3My4xOTYxQzIuNDEyNDEgNjguODA2IC02LjU0NzE5ZS0wNSA2Mi45Nzc5IC02LjU0NzE5ZS0wNSA1Ni43NzE2QzAuMDAwNTgxMTIgNDkuMDgyNSA0LjY0NzQxIDMzLjY3MiAyMy4yMjk1IDMzLjU0MkwzMy4wMTQgMzMuNDM1NUwzMy4wMTQgNDYuNTI3NkMyNy42NDM5IDQ2LjQ5OTEgMTQuMjQzNyA0OC40NjMyIDEzLjQ3MDggNTYuNzcxM0MxMy40NzA4IDU5LjM3MzYgMTQuNDczNyA2MS44Mzk3IDE2LjMxNzMgNjMuNzA5NUMxOC4xNjAxIDY1LjU1MjkgMjAuNjI2MSA2Ni41NTU4IDIzLjI1NTggNjYuNTU1OEMyOC40NTk1IDY2LjU1NTggMzMuMDE0IDYyLjAwMTggMzMuMDE0IDU2Ljc3MTNMMzMuMDE0IDQ2LjUyNzZMNDYuNDUyNCA0Ni41TDQ2LjQ1NzIgNTYuOTA2NEM0Ni40NTcyIDYyLjk3NzkgNDQuMDQ0NCA2OC44MDU4IDM5LjY1MzggNzMuMTk2MUMzNS4yNjI5IDc3LjU4NzYgMjkuNDM1MyA4MC4wMDAxIDIzLjIyODcgODAuMDAwMUMxNy4wMjIxIDgwLjAwMDEgMTEuMTk0MiA3Ny41ODc2IDYuODAzODggNzMuMTk2MVoiIGZpbGw9IndoaXRlIi8+PHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0tNi41NDcxOWUtMDUgMjMuMjI4NEMtNi41NDcxOWUtMDUgMTcuMDIyMSAyLjQxMjQxIDExLjE5NCA2LjgwMzg4IDYuODAzODdDMTEuMTk0MiAyLjQxMjQgMTcuMDIyMSAtNi40MTM1M2UtMDUgMjMuMjI4NyAtNi40MTM1M2UtMDVDMjkuNDM1MyAtNi40MTM1M2UtMDUgMzUuMjYyOSAyLjQxMjQgMzkuNjUzOCA2LjgwMzg3QzQ0LjA0NDQgMTEuMTk0MiA0Ni40NTcyIDE3LjAyMjEgNDYuNDU3MiAyMy4wOTM2TDQ2LjQ1MjQgMzMuNUwzMy4wMTQgMzMuNDcyNEwzMy4wMTQgMjMuMjI4N0MzMy4wMTQgMTcuOTk4MiAyOC40NTk1IDEzLjQ0NDIgMjMuMjU1OCAxMy40NDQyQzIwLjYyNjEgMTMuNDQ0MiAxOC4xNjAxIDE0LjQ0NzEgMTYuMzE3MyAxNi4yOTA1QzE0LjQ3MzcgMTguMTYwMyAxMy40NzA4IDIwLjYyNjQgMTMuNDcwOCAyMy4yMjg3QzEzLjgyOTIgMjcuMDgxMyAxNi45MDI5IDI5LjU2OTcgMjAuNjMyIDMxLjEyNzlDMTIuMDUxOCAzMS4xMjc5IDYuMzE5NiAzNS4zODE5IDQuNTI2MDEgMzcuNTA4OUMxLjEzNTI1IDMyLjcxMzcgMC4wMDAyNTQwNjMgMjcuMDI4MiAtNi41NDcxOWUtMDUgMjMuMjI4NFoiIGZpbGw9IndoaXRlIi8+PHJlY3QgeD0iMzMuMDEzMiIgeT0iMzMuNDM1NSIgd2lkdGg9IjEzLjQ0NDciIGhlaWdodD0iMTMuMTMwMiIgZmlsbD0id2hpdGUiLz48L3N2Zz4='; $page = add_menu_page( __( 'Admin Buddy', 'admin-buddy' ), __( 'Admin Buddy', 'admin-buddy' ), 'manage_options', 'admbud', [ $this, 'render_page' ], $icon_svg, 80 ); // First submenu: same slug as parent so WP doesn't create a duplicate. // Clicking parent "Admin Buddy" goes to last visited tab. add_submenu_page( 'admbud', __( 'Admin Buddy', 'admin-buddy' ), __( 'Admin Buddy', 'admin-buddy' ), 'manage_options', 'admbud', [ $this, 'render_page' ] ); // Explicit Modules submenu - always goes to Modules tab. add_submenu_page( 'admbud', __( 'Modules', 'admin-buddy' ), __( 'Modules', 'admin-buddy' ), 'manage_options', 'admbud&tab=modules', [ $this, 'render_page' ] ); // Plugin Data submenu. add_submenu_page( 'admbud', __( 'Plugin Data', 'admin-buddy' ), __( 'Plugin Data', 'admin-buddy' ), 'manage_options', 'admbud&tab=plugin-data', [ $this, 'render_page' ] ); } /** Optionally add Admin Buddy to the WP admin bar with submenu. */ public function maybe_register_adminbar( \WP_Admin_Bar $bar ): void { if ( admbud_get_option( 'admbud_show_in_adminbar', '0' ) !== '1' ) { return; } if ( ! current_user_can( 'manage_options' ) ) { return; } $base = admin_url( 'admin.php?page=admbud' ); $bar->add_node( [ 'id' => 'admbud', 'title' => '' . esc_html__( 'Admin Buddy', 'admin-buddy' ) . '', 'href' => $base, 'meta' => [ 'title' => __( 'Admin Buddy Settings', 'admin-buddy' ) ], ] ); // Modules link. $bar->add_node( [ 'parent' => 'admbud', 'id' => 'ab-bar-modules', 'title' => __( 'Modules', 'admin-buddy' ), 'href' => $base . '&tab=modules', ] ); // Enabled module links grouped with separators. $enabled = $this->get_enabled_tabs(); $tabs = $this->get_manageable_tabs(); $groups = [ 'interface', 'utilities', 'integrations' ]; foreach ( $groups as $group_key ) { $group_tabs = array_filter( $tabs, function ( $t ) use ( $group_key ) { return ( $t['group'] ?? '' ) === $group_key; } ); foreach ( $group_tabs as $gslug => $gtab ) { if ( ! in_array( $gslug, $enabled, true ) ) { continue; } $bar->add_node( [ 'parent' => 'admbud', 'id' => 'ab-bar-' . $gslug, 'title' => $gtab['label'], // Tab-less modules (e.g. Media Manager) carry their own 'url' // - they have no &tab= page, so link to where they actually live. 'href' => $gtab['url'] ?? ( $base . '&tab=' . $gslug ), ] ); } } // Always-visible manage tabs - same array as sidebar, kept in sync. $manage_tabs = [ 'quick-settings' => __( 'Quick Settings', 'admin-buddy' ), 'source' => __( 'Remote', 'admin-buddy' ), 'plugin-data' => __( 'Plugin Data', 'admin-buddy' ), ]; foreach ( $manage_tabs as $mtab_slug => $mtab_label ) { $bar->add_node( [ 'parent' => 'admbud', 'id' => 'ab-bar-' . $mtab_slug, 'title' => $mtab_label, 'href' => $base . '&tab=' . $mtab_slug, ] ); } } /** * Intercept options.php's post-save redirect on our settings pages. * * When any of our settings groups are saved, options.php redirects back * with ?settings-updated=true which triggers WP's settings_errors() system * and produces notices we can't control placement of. We swap that param * for our own ?admbud_notice=saved so we can render it exactly where we want. */ public function intercept_redirect( string $location ): string { if ( strpos( $location, 'page=admbud' ) !== false && strpos( $location, 'settings-updated=true' ) !== false ) { // This filter runs on the wp_redirect hook AFTER WordPress's // options.php pipeline has already verified the form's nonce // ($option_page-options) and saved the registered settings. The // 'settings-updated=true' marker in $location confirms that path // executed successfully — we only get here on a verified save. // // The $_POST reads below are read-only sanitisation of two custom // hidden fields (admbud_tab, admbud_subtab) that the form sets so // we can preserve nav state in the redirect URL. No state change, // no database writes — just URL massaging via add_query_arg(). // Force-clear the CSS cache on every settings save. // Belt-and-suspenders: updated_option should bust it per-key, // but some toggles (separator, gradient) can be missed if WP // skips the update when the value hasn't changed. Colours::maybe_bust_cache(); // Swap WP's settings-updated for our own notice param. $location = remove_query_arg( 'settings-updated', $location ); $location = add_query_arg( 'admbud_notice', 'saved', $location ); // Restore the main tab. Strip any tab= already in the URL (from the // HTTP referer) then add ours cleanly, so there's no duplication. // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified by WP options.php pipeline upstream (see comment above). $tab = sanitize_key( wp_unslash( $_POST['admbud_tab'] ?? '' ) ); if ( $tab !== '' ) { $location = remove_query_arg( 'tab', $location ); $location = add_query_arg( 'tab', $tab, $location ); } // Restore the active sub-tab. // phpcs:ignore WordPress.Security.NonceVerification.Missing -- nonce verified by WP options.php pipeline upstream (see comment above). $subtab = sanitize_key( wp_unslash( $_POST['admbud_subtab'] ?? '' ) ); if ( $subtab !== '' ) { $location = remove_query_arg( 'admbud_subtab', $location ); $location = add_query_arg( 'admbud_subtab', $subtab, $location ); } } return $location; } /** * Render our own inline success/error notice inside .ab-panel. * Reads ?admbud_notice= set by intercept_redirect() or handle_advanced(). * WP's settings_errors() system is never called for our settings pages. */ public function render_notice(): void { // phpcs:ignore WordPress.Security.NonceVerification if ( empty( $_GET['admbud_notice'] ) ) { return; } // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput // phpcs:ignore WordPress.Security.NonceVerification $key = sanitize_key( $_GET['admbud_notice'] ); // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput // These notices are rendered as JS toasts instead of HTML banners. $js_toasted = [ 'saved', 'import_ok', 'reset_ok', 'import_empty', 'import_read_fail', 'import_invalid' ]; if ( in_array( $key, $js_toasted, true ) ) { return; } // Demo mode blocked action notice. if ( $key === 'demo_blocked' ) { ?>
🔒
'boolean', 'sanitize_callback' => [ $this, 'sanitize_checkbox' ], 'default' => '0', ] ); } register_setting( 'admbud_core_group', 'admbud_core_custom_footer_enabled', [ 'type' => 'boolean', 'sanitize_callback' => [ $this, 'sanitize_checkbox' ], 'default' => '0', ] ); register_setting( 'admbud_core_group', 'admbud_core_custom_footer_text', [ 'type' => 'string', 'sanitize_callback' => 'wp_kses_post', 'default' => '', ] ); register_setting( 'admbud_core_group', 'admbud_wl_sidebar_logo_url', [ 'type' => 'string', 'sanitize_callback' => 'esc_url_raw', 'default' => '', ] ); register_setting( 'admbud_core_group', 'admbud_wl_sidebar_logo_width', [ 'type' => 'integer', 'sanitize_callback' => 'absint', 'default' => 84, ] ); register_setting( 'admbud_core_group', 'admbud_wl_sidebar_logo_height', [ 'type' => 'integer', 'sanitize_callback' => 'absint', 'default' => 0, ] ); register_setting( 'admbud_core_group', 'admbud_wl_sidebar_width', [ 'type' => 'integer', 'sanitize_callback' => [ $this, 'sanitize_sidebar_width' ], 'default' => 160, ] ); // Favicon: stored in WP's native 'site_icon' option (attachment ID). // We register under our group so it saves via our form; WP reads it natively. register_setting( 'admbud_core_group', 'admbud_wl_favicon_id', [ 'type' => 'integer', 'sanitize_callback' => function( $val ) { $id = absint( $val ); // Sync to WP native option so Settings > General reflects the change. update_option( 'site_icon', $id ); return $id; // stored as admbud_wl_favicon_id but ignored at read - we always read site_icon directly. }, 'default' => 0, ] ); register_setting( 'admbud_core_group', 'admbud_dashboard_role_pages', [ 'type' => 'string', 'sanitize_callback' => [ $this, 'sanitize_dashboard_role_pages' ], 'default' => '{}', ] ); // -- Phase 1 White Label options --------------------------------------- $wl_toggles = [ 'admbud_wl_remove_wp_links', 'admbud_wl_hide_wp_news', 'admbud_wl_footer_quote', ]; foreach ( $wl_toggles as $key ) { register_setting( 'admbud_core_group', $key, [ 'type' => 'boolean', 'sanitize_callback' => [ $this, 'sanitize_checkbox' ], 'default' => '0', ] ); } $wl_text_fields = [ 'admbud_wl_agency_name' => 'sanitize_text_field', 'admbud_wl_agency_url' => 'esc_url_raw', 'admbud_wl_greeting' => 'sanitize_text_field', 'admbud_wl_footer_version' => 'sanitize_text_field', ]; foreach ( $wl_text_fields as $key => $sanitizer ) { register_setting( 'admbud_core_group', $key, [ 'type' => 'string', 'sanitize_callback' => $sanitizer, 'default' => '', ] ); } register_setting( 'admbud_core_group', 'admbud_dashboard_keep_widgets', [ 'type' => 'string', 'sanitize_callback' => function( $val ) { $decoded = json_decode( wp_unslash( $val ), true ); if ( ! is_array( $decoded ) ) { return ''; } return wp_json_encode( array_map( 'sanitize_key', $decoded ) ); }, 'default' => '', ] ); register_setting( 'admbud_core_group', 'admbud_dashboard_custom_widgets', [ 'type' => 'string', 'sanitize_callback' => function( $val ) { $decoded = json_decode( wp_unslash( $val ), true ); if ( ! is_array( $decoded ) ) { return '[]'; } $clean = []; foreach ( $decoded as $w ) { if ( ! is_array( $w ) ) { continue; } $clean[] = [ 'title' => sanitize_text_field( $w['title'] ?? '' ), 'content' => wp_kses_post( $w['content'] ?? '' ), ]; } return wp_json_encode( $clean ); }, 'default' => '[]', ] ); register_setting( 'admbud_core_group', 'admbud_notices_suppress', [ 'type' => 'boolean', 'sanitize_callback' => [ $this, 'sanitize_checkbox' ], 'default' => '1', ] ); register_setting( 'admbud_core_group', 'admbud_show_in_adminbar', [ 'type' => 'boolean', 'sanitize_callback' => [ $this, 'sanitize_checkbox' ], 'default' => '0', ] ); register_setting( 'admbud_modules_group', 'admbud_modules_enabled_tabs', [ 'type' => 'string', 'sanitize_callback' => [ $this, 'sanitize_setup_tabs' ], 'default' => '', ] ); // -- Activity Log ------------------------------------------------------- register_setting( 'admbud_activity_log_group', 'admbud_activity_log_per_page', [ 'type' => 'integer', 'sanitize_callback' => 'absint', 'default' => 20, ] ); register_setting( 'admbud_activity_log_group', 'admbud_activity_log_retention', [ 'type' => 'integer', 'sanitize_callback' => 'absint', 'default' => 90, ] ); // -- Quick Settings ----------------------------------------------------- $qs_keys = [ 'admbud_qs_disable_emoji', 'admbud_qs_disable_jquery_migrate', 'admbud_qs_remove_feed_links', 'admbud_qs_remove_rsd', 'admbud_qs_remove_wlw', 'admbud_qs_remove_shortlink', 'admbud_qs_remove_restapi_link', 'admbud_qs_disable_embeds', 'admbud_qs_remove_version', 'admbud_qs_disable_xmlrpc', 'admbud_qs_disable_rest_api', 'admbud_qs_disable_file_edit', 'admbud_qs_disable_feeds', 'admbud_qs_disable_self_ping', 'admbud_qs_disable_comments_default', 'admbud_qs_duplicate_post', 'admbud_qs_user_last_seen', 'admbud_qs_allow_svg', ]; foreach ( $qs_keys as $qs_key ) { register_setting( 'admbud_quick_settings', $qs_key, [ 'type' => 'boolean', 'sanitize_callback' => [ $this, 'sanitize_checkbox' ], 'default' => '0', ] ); } register_setting( 'admbud_quick_settings', 'admbud_qs_svg_roles', [ 'type' => 'string', 'sanitize_callback' => 'sanitize_text_field', 'default' => 'administrator', ] ); // -- Bricks Builder Integration ---------------------------------------- register_setting( 'admbud_bricks_group', 'admbud_bricks_enabled', [ 'type' => 'boolean', 'sanitize_callback' => function( $val ) { $enabled = ( $val === '1' || $val === true || $val === 1 ); // If toggled off, revert Bricks to its default builder mode. if ( ! $enabled && class_exists( \Admbud\Bricks::class ) ) { \Admbud\Bricks::revert_bricks_builder_mode(); } return $enabled ? '1' : '0'; }, 'default' => '0', ] ); // Sub-toggle for the AB-to-Bricks colour mapping. When off, we leave // Bricks' --builder-* vars alone and revert builderMode to 'light' so // the builder uses Bricks' own native palette - even if other parts of // the integration (dynamic data, query loops, logo replacement) stay on. register_setting( 'admbud_bricks_group', 'admbud_bricks_color_mapping_enabled', [ 'type' => 'boolean', 'sanitize_callback' => function( $val ) { $enabled = ( $val === '1' || $val === true || $val === 1 ); if ( ! $enabled && class_exists( \Admbud\Bricks::class ) ) { \Admbud\Bricks::revert_bricks_builder_mode(); } return $enabled ? '1' : '0'; }, 'default' => '1', ] ); // Per-variable colour overrides - empty string = use derived value. $bricks_var_keys = [ 'builder_bg', 'builder_bg_2', 'builder_bg_3', 'builder_bg_accent', 'builder_color', 'builder_color_description', 'builder_color_accent', 'builder_color_accent_inverse', 'builder_color_knob', 'builder_border_color', 'bricks_tooltip_bg', 'bricks_tooltip_text', ]; foreach ( $bricks_var_keys as $vk ) { register_setting( 'admbud_bricks_group', 'admbud_bricks_' . $vk, [ 'type' => 'string', 'sanitize_callback' => function( $v ) { // Empty = "use derived". Non-empty must be a hex colour. $v = trim( $v ); if ( $v === '' ) { return ''; } return sanitize_hex_color( $v ) ?? ''; }, 'default' => '', ] ); } // -- Bricks Other Tweaks ----------------------------------------------- foreach ( [ 'admbud_bricks_hide_logo', 'admbud_bricks_hide_spinner' ] as $tweak_key ) { register_setting( 'admbud_bricks_group', $tweak_key, [ 'type' => 'boolean', 'sanitize_callback' => [ $this, 'sanitize_checkbox' ], 'default' => '0', ] ); } // Custom logo / spinner URLs - paired with the toggles above. Empty // string falls back to AB's default styled logo and CSS ring spinner. foreach ( [ 'admbud_bricks_custom_logo_url', 'admbud_bricks_custom_spinner_url' ] as $url_key ) { register_setting( 'admbud_bricks_group', $url_key, [ 'type' => 'string', 'sanitize_callback' => 'esc_url_raw', 'default' => '', ] ); } // -- Bricks BEM Generator ---------------------------------------------- foreach ( [ 'admbud_bricks_bem_enabled', 'admbud_bricks_bem_auto_sync_labels', 'admbud_bricks_bem_show_modifiers', ] as $bem_bool_key ) { register_setting( 'admbud_bricks_group', $bem_bool_key, [ 'type' => 'boolean', 'sanitize_callback' => [ $this, 'sanitize_checkbox' ], 'default' => ( $bem_bool_key === 'admbud_bricks_bem_auto_sync_labels' ) ? '1' : '0', ] ); } register_setting( 'admbud_bricks_group', 'admbud_bricks_bem_default_action', [ 'type' => 'string', 'sanitize_callback' => function( $v ) { $allowed = [ 'rename', 'remove', 'delete', 'keep', 'copy-id' ]; return in_array( $v, $allowed, true ) ? $v : 'rename'; }, 'default' => 'rename', ] ); register_setting( 'admbud_bricks_group', 'admbud_bricks_bem_blacklist_extra', [ 'type' => 'string', 'sanitize_callback' => function( $v ) { // Comma-separated list of property keys to treat as content // (not CSS styles) during the copy-id action. Bounded at // 64 tokens * 32 chars so hostile input can't balloon. $raw = (string) $v; $tokens = array_filter( array_map( 'trim', explode( ',', $raw ) ) ); $tokens = array_slice( $tokens, 0, 64 ); $tokens = array_map( function ( $t ) { return substr( sanitize_key( $t ), 0, 32 ); }, $tokens ); $tokens = array_values( array_filter( $tokens ) ); return implode( ',', $tokens ); }, 'default' => '', ] ); // -- Bricks Quick Insert ----------------------------------------------- register_setting( 'admbud_bricks_group', 'admbud_bricks_quick_enabled', [ 'type' => 'boolean', 'sanitize_callback' => [ $this, 'sanitize_checkbox' ], 'default' => '0', ] ); register_setting( 'admbud_bricks_group', 'admbud_bricks_quick_favourites', [ 'type' => 'string', 'sanitize_callback' => function( $v ) { // Stored as JSON array of slugs. Drop anything not in the // canonical catalog on save so the client never sees // unknown slugs. $catalog = class_exists( '\Admbud\BricksQuick' ) ? array_keys( \Admbud\BricksQuick::CATALOG ) : []; $raw = is_string( $v ) ? $v : wp_json_encode( $v ); $arr = json_decode( $raw, true ); if ( ! is_array( $arr ) ) { return '[]'; } $arr = array_values( array_filter( array_map( 'sanitize_key', $arr ), function ( $slug ) use ( $catalog ) { return in_array( $slug, $catalog, true ); } ) ); $arr = array_slice( $arr, 0, 30 ); return wp_json_encode( $arr ); }, 'default' => '[]', ] ); // -- Admin UI Colours -------------------------------------------------- $colour_opts = [ // hex-only: these feed into darken/lighten/hex_to_rgb_triplet calculations. 'admbud_colours_primary' => [ 'string', [ $this, 'sanitize_hex_color' ], Colours::DEFAULT_PRIMARY ], 'admbud_colours_menu_bg' => [ 'string', [ $this, 'sanitize_hex_color' ], Colours::DEFAULT_MENU_BG ], 'admbud_colours_sidebar_grad_from'=> [ 'string', [ $this, 'sanitize_hex_color' ], Colours::DEFAULT_SIDEBAR_GRAD_FROM ], 'admbud_colours_sidebar_grad_to' => [ 'string', [ $this, 'sanitize_hex_color' ], Colours::DEFAULT_SIDEBAR_GRAD_TO ], // rgba/hsla allowed: used directly in CSS output only. 'admbud_colours_secondary' => [ 'string', [ $this, 'sanitize_color' ], Colours::DEFAULT_SECONDARY ], 'admbud_colours_menu_text' => [ 'string', [ $this, 'sanitize_color' ], Colours::DEFAULT_MENU_TEXT ], 'admbud_colours_active_text' => [ 'string', [ $this, 'sanitize_color' ], Colours::DEFAULT_ACTIVE_TEXT ], 'admbud_colours_sep_color' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_body_bg' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_adminbar_text' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_adminbar_hover_bg' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_adminbar_submenu_bg'=> [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_adminbar_bg' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_adminbar_hover_text' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_adminbar_sub_text' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_adminbar_sub_hover_text' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_pill_maintenance' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_pill_coming_soon' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_pill_noindex' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_pill_admin_buddy' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_submenu_text' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_hover_text' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_hover_bg' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_active_bg' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_active_parent_text' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_submenu_bg' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_submenu_hover_bg' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_submenu_hover_text' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_submenu_active_bg' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_submenu_active_text' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_adminbar_sub_hover_bg' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_shadow_colour' => [ 'string', [ $this, 'sanitize_color' ], '' ], // -- Content area tokens ------------------------------------------ 'admbud_colours_content_heading' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_content_text' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_content_link' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_content_link_hover' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_table_header_bg' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_table_header_text' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_table_header_link' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_table_row_bg' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_table_row_text' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_table_row_alt_bg' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_table_row_alt_text' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_table_row_hover' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_table_border' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_table_row_separator' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_table_action_link' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_table_title_link' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_input_bg' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_input_border' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_input_focus' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_btn_primary_bg' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_btn_primary_text' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_btn_primary_hover' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_btn_secondary_bg' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_postbox_bg' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_postbox_header_bg' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_postbox_border' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_postbox_text' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_notice_bg' => [ 'string', [ $this, 'sanitize_color' ], '' ], 'admbud_colours_sidebar_gradient' => [ 'boolean', [ $this, 'sanitize_checkbox' ], '0' ], 'admbud_colours_sidebar_grad_dir' => [ 'string', [ $this, 'sanitize_grad_direction' ], Colours::DEFAULT_SIDEBAR_GRAD_DIR ], 'admbud_colours_menu_item_sep' => [ 'boolean', [ $this, 'sanitize_checkbox' ], '1' ], 'admbud_colours_ui_radius' => [ 'string', [ $this, 'sanitize_ui_radius' ], 'off' ], ]; foreach ( $colour_opts as $key => [ $type, $cb, $default ] ) { register_setting( 'admbud_colours_group', $key, [ 'type' => $type, 'sanitize_callback' => $cb, 'default' => $default, ] ); } // -- CSS Exclusions ---------------------------------------------------- register_setting( 'admbud_css_exclusions_group', 'admbud_css_exclusions', [ 'type' => 'string', 'sanitize_callback' => [ $this, 'sanitize_css_exclusions' ], 'default' => '', ] ); // -- Login branding ---------------------------------------------------- $login_opts = [ 'admbud_login_logo_url' => [ 'string', 'esc_url_raw', '' ], 'admbud_login_logo_width' => [ 'integer', [ $this, 'sanitize_logo_width' ], 84 ], 'admbud_login_logo_height' => [ 'integer', [ $this, 'sanitize_logo_height' ], 0 ], 'admbud_login_card_position' => [ 'string', [ $this, 'sanitize_card_position' ], 'center' ], 'admbud_login_bg_type' => [ 'string', [ $this, 'sanitize_bg_type' ], 'solid' ], 'admbud_login_bg_color' => [ 'string', [ $this, 'sanitize_hex_color' ], Colours::DEFAULT_PAGE_BG ], 'admbud_login_grad_from' => [ 'string', [ $this, 'sanitize_hex_color' ], Colours::DEFAULT_SIDEBAR_GRAD_FROM ], 'admbud_login_grad_to' => [ 'string', [ $this, 'sanitize_hex_color' ], Colours::DEFAULT_SIDEBAR_GRAD_TO ], 'admbud_login_grad_direction' => [ 'string', [ $this, 'sanitize_grad_direction' ], 'to bottom right' ], 'admbud_login_bg_image_url' => [ 'string', 'esc_url_raw', '' ], 'admbud_login_bg_overlay_color' => [ 'string', [ $this, 'sanitize_hex_color' ], '#000000' ], 'admbud_login_bg_overlay_opacity' => [ 'integer', [ $this, 'sanitize_overlay_opacity' ], 30 ], 'admbud_login_card_bg_color' => [ 'string', [ $this, 'sanitize_hex_color' ], '#ffffff' ], 'admbud_login_card_text_auto' => [ 'string', [ $this, 'sanitize_checkbox' ], '1' ], 'admbud_login_card_text_color' => [ 'string', [ $this, 'sanitize_hex_color' ], '#3c434a' ], 'admbud_login_card_width' => [ 'integer', [ $this, 'sanitize_card_width' ], 400 ], 'admbud_login_btn_auto' => [ 'string', [ $this, 'sanitize_checkbox' ], '1' ], 'admbud_login_btn_bg' => [ 'string', [ $this, 'sanitize_hex_color' ], '#2271b1' ], ]; foreach ( $login_opts as $key => [ $type, $cb, $default ] ) { register_setting( 'admbud_login_group', $key, [ 'type' => $type, 'sanitize_callback' => $cb, 'default' => $default, ] ); } // -- Maintenance ------------------------------------------------------- $maint_opts = [ 'admbud_maintenance_mode' => [ 'string', [ $this, 'sanitize_maintenance_mode' ], 'off' ], // Coming Soon 'admbud_coming_soon_title' => [ 'string', 'sanitize_text_field', __( 'Coming Soon', 'admin-buddy' ) ], 'admbud_coming_soon_message' => [ 'string', 'wp_kses_post', __( "We're working on something exciting. Stay tuned!", 'admin-buddy' ) ], 'admbud_cs_bg_type' => [ 'string', [ $this, 'sanitize_bg_type' ], 'solid' ], 'admbud_cs_bg_color' => [ 'string', [ $this, 'sanitize_hex_color' ], Colours::DEFAULT_PAGE_BG ], 'admbud_cs_grad_from' => [ 'string', [ $this, 'sanitize_hex_color' ], Colours::DEFAULT_SIDEBAR_GRAD_FROM ], 'admbud_cs_grad_to' => [ 'string', [ $this, 'sanitize_hex_color' ], Colours::DEFAULT_SIDEBAR_GRAD_TO ], 'admbud_cs_grad_direction' => [ 'string', [ $this, 'sanitize_grad_direction' ], 'to bottom right' ], 'admbud_cs_bg_image_url' => [ 'string', 'esc_url_raw', '' ], 'admbud_cs_bg_overlay_color' => [ 'string', [ $this, 'sanitize_hex_color' ], '#000000' ], 'admbud_cs_bg_overlay_opacity' => [ 'integer', [ $this, 'sanitize_overlay_opacity' ], 30 ], // Maintenance 'admbud_maintenance_title' => [ 'string', 'sanitize_text_field', __( 'Under Maintenance', 'admin-buddy' ) ], 'admbud_maintenance_message' => [ 'string', 'wp_kses_post', __( "We're performing scheduled maintenance. We'll be back shortly!", 'admin-buddy' ) ], 'admbud_maint_bg_type' => [ 'string', [ $this, 'sanitize_bg_type' ], 'solid' ], 'admbud_maint_bg_color' => [ 'string', [ $this, 'sanitize_hex_color' ], Colours::DEFAULT_PAGE_BG ], 'admbud_maint_grad_from' => [ 'string', [ $this, 'sanitize_hex_color' ], Colours::DEFAULT_SIDEBAR_GRAD_FROM ], 'admbud_maint_grad_to' => [ 'string', [ $this, 'sanitize_hex_color' ], Colours::DEFAULT_SIDEBAR_GRAD_TO ], 'admbud_maint_grad_direction' => [ 'string', [ $this, 'sanitize_grad_direction' ], 'to bottom right' ], 'admbud_maint_bg_image_url' => [ 'string', 'esc_url_raw', '' ], 'admbud_maint_bg_overlay_color' => [ 'string', [ $this, 'sanitize_hex_color' ], '#000000' ], 'admbud_maint_bg_overlay_opacity' => [ 'integer', [ $this, 'sanitize_overlay_opacity' ], 30 ], 'admbud_maintenance_bypass_urls' => [ 'string', [ $this, 'sanitize_bypass_urls' ], '' ], // Text colours for intercept pages 'admbud_cs_text_color' => [ 'string', [ $this, 'sanitize_hex_color' ], Colours::DEFAULT_PAGE_TEXT ], 'admbud_cs_message_color' => [ 'string', [ $this, 'sanitize_hex_color' ], Colours::DEFAULT_PAGE_MESSAGE ], 'admbud_maint_text_color' => [ 'string', [ $this, 'sanitize_hex_color' ], Colours::DEFAULT_PAGE_TEXT ], 'admbud_maint_message_color' => [ 'string', [ $this, 'sanitize_hex_color' ], Colours::DEFAULT_PAGE_MESSAGE ], ]; foreach ( $maint_opts as $key => [ $type, $cb, $default ] ) { register_setting( 'admbud_maintenance_group', $key, [ 'type' => $type, 'sanitize_callback' => $cb, 'default' => $default, ] ); } // -- SMTP -------------------------------------------------------------- $smtp_opts = [ 'admbud_smtp_enabled' => [ 'boolean', [ $this, 'sanitize_checkbox' ], '0' ], 'admbud_smtp_mailer' => [ 'string', [ $this, 'sanitize_smtp_mailer' ], 'smtp' ], 'admbud_smtp_host' => [ 'string', 'sanitize_text_field', '' ], 'admbud_smtp_port' => [ 'integer', [ $this, 'sanitize_smtp_port' ], 587 ], 'admbud_smtp_username' => [ 'string', 'sanitize_text_field', '' ], 'admbud_smtp_encryption' => [ 'string', [ $this, 'sanitize_smtp_enc' ], 'tls' ], 'admbud_smtp_auth' => [ 'boolean', [ $this, 'sanitize_checkbox' ], '1' ], 'admbud_smtp_from_name' => [ 'string', 'sanitize_text_field', '' ], 'admbud_smtp_from_email' => [ 'string', 'sanitize_email', '' ], 'admbud_smtp_fallback' => [ 'boolean', [ $this, 'sanitize_checkbox' ], '0' ], // legacy - kept for back-compat 'admbud_smtp_disable_ssl_verify'=> [ 'boolean', [ $this, 'sanitize_checkbox' ], '0' ], 'admbud_smtp_preset' => [ 'string', 'sanitize_key', 'custom' ], ]; foreach ( $smtp_opts as $key => [ $type, $cb, $default ] ) { register_setting( 'admbud_smtp_group', $key, [ 'type' => $type, 'sanitize_callback' => $cb, 'default' => $default, ] ); } // Password is handled separately - encrypted before storage. add_filter( 'pre_update_option_ab_smtp_group', [ $this, 'maybe_encrypt_smtp_password' ] ); } // ============================================================================ // ASSETS // ============================================================================ public function enqueue_assets( string $hook ): void { // Hook suffix follows the menu slug ('admbud'), not the plugin folder name. if ( ! in_array( $hook, [ 'settings_page_admbud', 'toplevel_page_admbud' ], true ) ) return; // Dev cache-buster: in non-production envs, suffix the plugin // version with a timestamp so every page load fetches fresh // CSS/JS. Edits to admin.css / ab-dropdown.js / etc. show up // without needing a plugin-version bump (the user opposes per-fix // bumps). Production envs keep the canonical ADMBUD_VERSION so // HTTP caches + CDNs can cache normally. // Gate covers both classic `WP_DEBUG` and modern `wp_get_environment_type()`. $v = ADMBUD_VERSION; $is_dev = ( defined( 'WP_DEBUG' ) && WP_DEBUG ); if ( ! $is_dev && function_exists( 'wp_get_environment_type' ) ) { $env = wp_get_environment_type(); $is_dev = ( $env === 'local' || $env === 'development' ); } if ( $is_dev ) { $v .= '.' . time(); } $url = ADMBUD_URL . 'assets/'; $js_url = ADMBUD_URL . 'assets/js/'; // Phase 1.1.0: minified files removed - all assets are unminified source. // WordPress caching / CDN handles production optimisation. $min_js = ''; $min_css = ''; // Tokens must load before component CSS. wp_enqueue_style( 'admbud-tokens', $url . 'tokens.css', [], $v ); // The WP-admin-scheme accent override (used when the Colours module is // OFF) is attached to `admbud-tokens` centrally by attach_scheme_accent() // - see that method. It applies on every AB surface, not just this // settings page, so off-canvas panels match the admin scheme too. // Core shared CSS - always loaded. wp_enqueue_style( 'admbud-core', $url . 'admin.css', [ 'admbud-tokens' ], $v ); wp_enqueue_style( 'admbud-dropdown', $url . "ab-dropdown{$min_css}.css", [ 'admbud-core' ], $v ); wp_enqueue_style( 'admbud-datepicker', $url . "ab-datepicker{$min_css}.css", [ 'admbud-core' ], $v ); wp_enqueue_style( 'wp-color-picker' ); wp_enqueue_script( 'wp-color-picker' ); wp_enqueue_script( 'admbud-dropdown', $js_url . 'ab-dropdown.js', [], $v, true ); wp_enqueue_script( 'admbud-datepicker', $js_url . 'ab-datepicker.js', [], $v, true ); wp_enqueue_media(); // Core shared JS - Vanilla ES6+, no jQuery dependency. wp_enqueue_script( 'admbud-core', $js_url . 'admin.js', [ 'wp-color-picker', 'admbud-dropdown' ], $v, true ); // Tab-specific assets - only loaded on the relevant tab. $active_tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : 'adminui'; // phpcs:ignore WordPress.Security.NonceVerification switch ( $active_tab ) { case 'smtp': wp_enqueue_style( 'admbud-tab-smtp', $url . "tab-smtp{$min_css}.css", [ 'admbud-core' ], $v ); wp_enqueue_script( 'admbud-tab-smtp', $js_url . 'tab-smtp.js', [ 'admbud-core' ], $v, true ); break; case 'roles': wp_enqueue_style( 'admbud-tab-roles', $url . "tab-roles{$min_css}.css", [ 'admbud-core' ], $v ); wp_enqueue_script( 'admbud-tab-roles', $js_url . 'tab-roles.js', [ 'admbud-core' ], $v, true ); break; case 'quick-settings': wp_enqueue_script( 'admbud-tab-quick-settings', $js_url . 'tab-quick-settings.js', [ 'admbud-core' ], $v, true ); wp_localize_script( 'admbud-tab-quick-settings', 'admbudQs', [ 'nonce' => wp_create_nonce( 'admbud_qs_nonce' ), 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'i18n' => [ 'enabled' => __( 'Setting enabled.', 'admin-buddy' ), 'disabled' => __( 'Setting disabled.', 'admin-buddy' ), 'failed' => __( 'Failed to save. Please try again.', 'admin-buddy' ), 'autoEnabled' => __( "Sidebar User Menu turned on so you can still navigate. Toggle off below if you don't want it.", 'admin-buddy' ), 'rolesSaved' => __( 'Roles saved.', 'admin-buddy' ), 'rolesFailed' => __( 'Failed to save roles.', 'admin-buddy' ), 'allEnabled' => __( 'All settings enabled.', 'admin-buddy' ), 'allDisabled' => __( 'All settings disabled.', 'admin-buddy' ), 'confirmEnableTitle' => __( 'Enable all Quick Settings?', 'admin-buddy' ), 'confirmEnableBody' => __( 'All 16 settings will be turned on. You can disable individual ones afterwards.', 'admin-buddy' ), 'confirmEnableYes' => __( 'Yes, enable all', 'admin-buddy' ), 'confirmDisableTitle' => __( 'Disable all Quick Settings?', 'admin-buddy' ), 'confirmDisableBody' => __( 'All 16 settings will be turned off.', 'admin-buddy' ), 'confirmDisableYes' => __( 'Yes, disable all', 'admin-buddy' ), ], ] ); break; case 'modules': wp_enqueue_script( 'wp-element' ); wp_enqueue_script( 'admbud-setup-modules', $js_url . 'setup-modules.js', [ 'admbud-core', 'wp-element' ], $v, true ); $manageable = $this->get_manageable_tabs(); $enabled = $this->get_enabled_tabs(); $modules_data = []; foreach ( $manageable as $slug => $tab ) { $modules_data[] = [ 'slug' => $slug, 'label' => $tab['label'], 'icon' => $tab['icon'], 'group' => $tab['group'] ?? 'interface', 'enabled' => in_array( $slug, $enabled, true ), 'always_on' => false, ]; } // Use wp_add_inline_script + wp_json_encode to preserve boolean types. // wp_localize_script stringifies booleans (false→"", true→"1") which // breaks strict equality checks in nested data structures. $setup_data = [ 'modules' => $modules_data, 'nonce' => wp_create_nonce( 'admbud_modules_toggle' ), 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'maintenanceMode' => admbud_get_option( 'admbud_maintenance_mode', 'off' ), 'maintenanceUrl' => admin_url( 'admin.php?page=admbud&tab=maintenance' ), ]; wp_add_inline_script( 'admbud-setup-modules', 'var admbudSetupData=' . wp_json_encode( $setup_data ) . ';', 'before' ); $icons_map = []; foreach ( $manageable as $slug => $tab ) { $icons_map[ $slug ] = $tab['icon']; } wp_localize_script( 'admbud-setup-modules', 'admbudIcons', $icons_map ); break; } // Shared localised strings - passed to admbud-core but also available to tab scripts. wp_localize_script( 'admbud-core', 'admbudSettings', [ 'chooseImageTitle' => __( 'Choose Image', 'admin-buddy' ), 'useImageText' => __( 'Use this image', 'admin-buddy' ), 'copiedText' => __( '✓ Copied', 'admin-buddy' ), 'settingsSaved' => __( 'Settings saved.', 'admin-buddy' ), 'formSaveConfirmTitle' => __( 'Save changes?', 'admin-buddy' ), 'formSaveConfirmBody' => __( 'Your settings will be saved.', 'admin-buddy' ), 'regenerateConfirm' => __( 'This will invalidate the current emergency URL. Continue?', 'admin-buddy' ), 'regeneratingText' => __( 'Regenerating…', 'admin-buddy' ), 'regenerateText' => __( 'Regenerate', 'admin-buddy' ), 'regeneratedText' => __( '✓ Token regenerated', 'admin-buddy' ), 'errorText' => __( '✗ Something went wrong.', 'admin-buddy' ), // Primary colour - used by login preview button when #admbud_colours_primary input is not on the page. 'primaryColour' => admbud_get_option( 'admbud_colours_primary', \Admbud\Colours::DEFAULT_PRIMARY ), // Import confirmation modal strings. 'importConfirmTitle' => __( 'Import Settings?', 'admin-buddy' ), 'importConfirmBody' => __( 'This will replace all current settings with the contents of the uploaded file. This cannot be undone.', 'admin-buddy' ), // Unsaved changes warning. 'unsavedTitle' => __( 'Unsaved changes', 'admin-buddy' ), 'unsavedBody' => __( 'You have unsaved changes that will be lost if you leave this tab.', 'admin-buddy' ), 'unsavedLeave' => __( 'Leave', 'admin-buddy' ), // SMTP. 'smtpTesting' => __( 'Sending…', 'admin-buddy' ), 'smtpClearConfirmTitle' => __( 'Clear Email Log?', 'admin-buddy' ), 'smtpClearConfirmBody' => __( 'All logged entries will be permanently deleted.', 'admin-buddy' ), // Roles. 'roleSaving' => __( 'Saving…', 'admin-buddy' ), 'roleSaved' => __( 'Saved.', 'admin-buddy' ), 'roleSaveConfirmTitle' => __( 'Save capabilities?', 'admin-buddy' ), 'roleDeleteConfirmTitle' => __( 'Delete Role?', 'admin-buddy' ), 'roleDeleteConfirmBody' => __( 'Users with this role will be moved to Subscriber. This cannot be undone.', 'admin-buddy' ), 'roleResetConfirmTitle' => __( 'Reset Role?', 'admin-buddy' ), 'roleResetConfirmBody' => __( 'This will restore the default capabilities for this role.', 'admin-buddy' ), // Menu. 'menuSaving' => __( 'Saving…', 'admin-buddy' ), 'menuSaved' => __( 'Saved.', 'admin-buddy' ), // Custom Pages. 'newPage' => __( 'New Page', 'admin-buddy' ), 'editPage' => __( 'Edit Page', 'admin-buddy' ), 'pageSaved' => __( 'Page saved.', 'admin-buddy' ), 'pageDeleted' => __( 'Page deleted.', 'admin-buddy' ), 'pageEnabled' => __( 'Page enabled.', 'admin-buddy' ), 'pageDisabled' => __( 'Page disabled.', 'admin-buddy' ), 'titleRequired' => __( 'Page title is required.', 'admin-buddy' ), 'deletePage' => __( 'Delete page?', 'admin-buddy' ), /* translators: %s: page title */ 'deletePageBody' => __( 'Delete "%s"? This cannot be undone.', 'admin-buddy' ), // Theme URLs - used by Bricks integration modal redirects. 'themeInstallUrl' => esc_url( admin_url( 'theme-install.php?browse=popular' ) ), 'themesUrl' => esc_url( admin_url( 'themes.php' ) ), // -- Toast / notification strings used across all tab JS files -- 'importSuccess' => __( 'Settings imported successfully.', 'admin-buddy' ), 'resetSuccess' => __( 'Settings reset to defaults.', 'admin-buddy' ), 'importEmpty' => __( 'Import failed: no file selected.', 'admin-buddy' ), 'importBadFile' => __( 'Import failed: invalid file.', 'admin-buddy' ), 'saveFailed' => __( 'Save failed.', 'admin-buddy' ), 'requestFailed' => __( 'Request failed.', 'admin-buddy' ), 'toggleFailed' => __( 'Toggle failed.', 'admin-buddy' ), 'uploadFailed' => __( 'Upload failed.', 'admin-buddy' ), 'iconUploaded' => __( 'Icon uploaded.', 'admin-buddy' ), 'iconRenamed' => __( 'Icon renamed.', 'admin-buddy' ), 'colourSettingSaved' => __( 'Colour setting saved.', 'admin-buddy' ), 'slugCopied' => __( 'Slug copied.', 'admin-buddy' ), 'onlySvgAllowed' => __( 'Only SVG files are supported.', 'admin-buddy' ), 'roleCreated' => __( 'Role created.', 'admin-buddy' ), 'roleDeleted' => __( 'Role deleted.', 'admin-buddy' ), 'roleRenamed' => __( 'Role renamed.', 'admin-buddy' ), 'roleDefaultsRestored' => __( 'Defaults restored.', 'admin-buddy' ), 'menuSaveConfirmTitle' => __( 'Save menu configuration?', 'admin-buddy' ), /* translators: %s: role name */ 'menuSaveConfirmBody' => __( 'Save settings for "%s".', 'admin-buddy' ), 'menuResetConfirmTitle' => __( 'Reset to defaults?', 'admin-buddy' ), 'paletteGenerated' => __( 'Palette generated.', 'admin-buddy' ), 'preserveColours' => __( 'Preserve colours', 'admin-buddy' ), 'copySlug' => __( 'Copy', 'admin-buddy' ), 'deleteIcon' => __( 'Delete', 'admin-buddy' ), 'clickToRename' => __( 'Click to rename', 'admin-buddy' ), 'saveChanges' => __( 'Save Changes', 'admin-buddy' ), 'removeWidget' => __( 'Remove widget?', 'admin-buddy' ), 'removeWidgetBody' => __( 'This widget will be removed. Save to apply.', 'admin-buddy' ), // Setup modules. 'enableAllModules' => __( 'Enable all', 'admin-buddy' ), 'disableAllModules' => __( 'Disable all', 'admin-buddy' ), 'enableModulesConfirm' => __( 'All modules in this group will be enabled.', 'admin-buddy' ), 'disableModulesConfirm' => __( 'All modules will be removed from the navigation.', 'admin-buddy' ), 'moduleAlwaysOn' => __( 'This module cannot be disabled', 'admin-buddy' ), 'disableModule' => __( 'Disable', 'admin-buddy' ), 'enableModule' => __( 'Enable', 'admin-buddy' ), 'maintenanceWarning' => __( 'Turn it off in the Maintenance tab first, then you can disable this module.', 'admin-buddy' ), 'goToMaintenance' => __( 'Go to Maintenance', 'admin-buddy' ), // Group labels. 'groupInterface' => __( 'Interface', 'admin-buddy' ), 'groupUtilities' => __( 'Utilities', 'admin-buddy' ), 'groupIntegrations' => __( 'Integrations', 'admin-buddy' ), // Quick Settings AJAX nonce. 'qsNonce' => wp_create_nonce( 'admbud_qs_nonce' ), 'ajaxUrl' => admin_url( 'admin-ajax.php' ), // General-purpose nonce for nav reorder etc. 'admbudNonce' => wp_create_nonce( 'admbud_nonce' ), ] ); } public function action_links( array $links ): array { array_unshift( $links, sprintf( '%s', esc_url( admin_url( 'admin.php?page=admbud' ) ), esc_html__( 'Settings', 'admin-buddy' ) ) ); return $links; } /** * Add Subresource Integrity (SRI) attributes to vendor scripts. * Protects against CDN/supply-chain tampering (I-2 hardening). * * @param string $tag HTML script tag. * @param string $handle Script handle name. * @return string Modified tag with integrity + crossorigin attributes. */ public function add_sri_attributes( string $tag, string $handle ): string { $sri_map = [ ]; if ( isset( $sri_map[ $handle ] ) && strpos( $tag, 'integrity=' ) === false ) { $tag = str_replace( ' src=', ' integrity="' . esc_attr( $sri_map[ $handle ] ) . '" crossorigin="anonymous" src=', $tag ); } return $tag; } // ============================================================================ // TOOLS ACTIONS (export / import / reset variants) // ============================================================================ // ============================================================================ // PAGE RENDER // ============================================================================ /** * Redirect to the last visited tab+subtab if arriving at admin-buddy without ?tab=. * Runs on admin_init (priority 1) - before any output so headers can be sent. */ public function maybe_redirect_to_last_tab(): void { // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( ( $_GET['page'] ?? '' ) !== 'admin-buddy' ) { return; } // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( isset( $_GET['tab'] ) ) { return; } // phpcs:ignore WordPress.Security.ValidatedSanitizedInput,WordPress.Security.NonceVerification // Don't redirect if there's a POST action (reset/import/export) // phpcs:ignore WordPress.Security.NonceVerification.Missing,WordPress.Security.NonceVerification.Recommended if ( ! empty( $_POST['admbud_action'] ) || ! empty( $_GET['admbud_action'] ) ) { return; } // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput // Don't redirect after a save/reset/import (notice in URL) // phpcs:ignore WordPress.Security.NonceVerification.Recommended if ( ! empty( $_GET['admbud_notice'] ) || ! empty( $_GET['settings-updated'] ) ) { return; } // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput if ( empty( $_COOKIE['admbud_last_tab'] ) ) { return; } $saved_tab = sanitize_key( wp_unslash( $_COOKIE['admbud_last_tab'] ) ); if ( ! $saved_tab ) { return; } $url = admin_url( 'admin.php?page=admbud&tab=' . $saved_tab ); $sub_cookie = 'admbud_subtab_' . $saved_tab; if ( ! empty( $_COOKIE[ $sub_cookie ] ) ) { $url = add_query_arg( 'admbud_subtab', sanitize_key( wp_unslash( $_COOKIE[ $sub_cookie ] ) ), $url ); } wp_safe_redirect( $url ); exit; } /** * Read the active subtab from GET params or cookie (set by JS). * Allows PHP to render the correct pane on first paint, eliminating flicker. */ /** * Return all registered tab slugs → labels. * Used by Search to build the dynamic index without duplicating the tab list. */ public static function get_all_tab_labels(): array { return [ 'adminui' => __( 'White Label', 'admin-buddy' ), 'colours' => __( 'Colours', 'admin-buddy' ), 'login' => __( 'Login', 'admin-buddy' ), 'maintenance' => __( 'Maintenance', 'admin-buddy' ), 'smtp' => __( 'SMTP', 'admin-buddy' ), 'roles' => __( 'User Roles', 'admin-buddy' ), 'modules' => __( 'Modules', 'admin-buddy' ), 'plugin-data' => __( 'Plugin Data', 'admin-buddy' ), 'quick-settings' => __( 'Quick Settings', 'admin-buddy' ), ]; } public static function get_active_subtab( string $tab_slug, string $default ): string { // Read-only navigation state: returns which subtab to render. No state // change, no database writes, no nonce required. The value is // sanitize_key()'d before returning. Cookie fallback persists the user's // last-viewed subtab between requests. if ( ! empty( $_GET['admbud_subtab'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput return sanitize_key( wp_unslash( $_GET['admbud_subtab'] ) ); // phpcs:ignore WordPress.Security.NonceVerification } $cookie_key = 'admbud_subtab_' . $tab_slug; if ( ! empty( $_COOKIE[ $cookie_key ] ) ) { return sanitize_key( wp_unslash( $_COOKIE[ $cookie_key ] ) ); } return $default; } /** * Enqueue assets that must be available on EVERY admin page, not just the AB settings page. * - admbud-icon-inject (script): provides window.AdmbudIcon.injectSidebarIcons(), * called by inline scripts that each module appends via wp_add_inline_script(). * - admbud-icon-inject (style): empty handle used as the attachment point for * per-module inline CSS that targets the WP admin sidebar icons. */ public function enqueue_global_assets(): void { wp_enqueue_script( 'admbud-icon-inject', ADMBUD_URL . 'assets/js/ab-icon-inject.js', [], ADMBUD_VERSION, true // footer ); wp_register_style( 'admbud-icon-inject', false, [], ADMBUD_VERSION ); wp_enqueue_style( 'admbud-icon-inject' ); } /** * Attach the WP-admin-scheme accent override to the shared `admbud-tokens` * stylesheet - once, regardless of which module enqueued it. * * Hooked late on both admin_enqueue_scripts and wp_enqueue_scripts so every * AB surface picks up the accent from ONE source: the settings page, the * Checklist panel on any admin page or the front-end admin bar, Pro Option * Pages, the Command palette. Previously the accent was emitted only by * enqueue_assets() and scoped to .ab-wrap, so panels rendered outside that * wrapper fell back to the hardcoded Fresh blue. */ public function attach_scheme_accent(): void { if ( ! wp_style_is( 'admbud-tokens', 'registered' ) ) { return; // No AB surface loaded on this request. } $css = self::scheme_accent_css(); if ( '' !== $css ) { wp_add_inline_style( 'admbud-tokens', $css ); } } /** * True when the Colours module is active. It then owns every AB accent - * admin chrome AND the Login / Maintenance pages - so the WP-scheme * fallbacks in this group stand down. */ public static function colours_module_active(): bool { $enabled = admbud_enabled_modules(); return is_array( $enabled ) && in_array( 'colours', $enabled, true ); } /** * Resolve the active WP admin colour scheme's accent as a hex string. * * Shared by scheme_accent_css() (admin chrome) and scheme_page_palette() * (Login / Maintenance pages) - one resolver, no duplicated logic. * * On the front end admin_init never fires, so the colour schemes are not * registered yet; we register them on demand. With no logged-in user * get_user_option() returns false and we fall back to core's * version-dependent default scheme. */ public static function scheme_accent_hex(): string { global $_wp_admin_css_colors; if ( empty( $_wp_admin_css_colors ) && function_exists( 'register_admin_color_schemes' ) ) { register_admin_color_schemes(); } // WP 7.0 made 'modern' the default admin colour scheme (it was 'fresh' // on 6.x). Mirror core's version-dependent default for users who have // never explicitly picked a scheme. $default = version_compare( get_bloginfo( 'version' ), '7.0', '>=' ) ? 'modern' : 'fresh'; $scheme = get_user_option( 'admin_color' ) ?: $default; // A scheme's `colors` array ends [..., accent, accent-light] - the // accent is the second-to-last entry. The index is NOT fixed: classic // 4-colour schemes (Fresh, Coffee, ...) carry it at [2], but WP 7.0's // 3-colour 'modern' scheme carries it at [1]. count() - 2 resolves it // for every registered scheme. $colors = $_wp_admin_css_colors[ $scheme ]->colors ?? null; if ( is_array( $colors ) && count( $colors ) >= 2 ) { $hex = (string) $colors[ count( $colors ) - 2 ]; if ( preg_match( '/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/', $hex ) ) { return $hex; } } return '#2271b1'; // Fresh accent; hard fallback if the scheme is gone. } /** * The `:root` custom-property block that points AB chrome at the WP admin * scheme accent, used when the Colours module is OFF. Emitted at :root (not * .ab-wrap) so off-canvas panels inherit it too. The derived accent vars * are redeclared because tokens.css computes them at :root from * var(--ab-primary, …) - overriding --ab-primary alone would not * re-substitute the :root copies. See attach_scheme_accent(). * * @return string CSS, or '' when the Colours module owns the accent. */ public static function scheme_accent_css(): string { if ( self::colours_module_active() ) { return ''; } $a = esc_attr( self::scheme_accent_hex() ); return ':root{' . '--ab-primary:' . $a . ';' . '--ab-secondary:' . $a . ';' . '--ab-accent:' . $a . ';' . '--ab-accent-hover:color-mix(in srgb,' . $a . ' 85%,#000);' . '--ab-accent-subtle:color-mix(in srgb,' . $a . ' 8%,transparent);' . '--ab-border-focus:' . $a . ';' . '}'; } /** * Dark, accent-tinted palette for the Login / Maintenance / Coming-Soon * pages, derived from the WP admin scheme accent. Used when the Colours * module is OFF so those public pages follow the admin scheme instead of * the brand-violet defaults. ONE source, so both pages render identically. * * @return array{bg:string,grad_from:string,grad_to:string,heading:string,message:string} */ public static function scheme_page_palette(): array { $a = self::scheme_accent_hex(); return [ 'bg' => self::mix_hex( $a, '#0d0d14', 12 ), // near-black, accent-tinted 'grad_from' => self::mix_hex( $a, '#0d0d14', 24 ), // upper gradient stop 'grad_to' => self::mix_hex( $a, '#0d0d14', 12 ), // lower gradient stop 'heading' => self::mix_hex( $a, '#ffffff', 14 ), // near-white heading 'message' => self::mix_hex( $a, '#ffffff', 45 ), // softer body copy ]; } /** * Blend two hex colours in sRGB. $weight_a is the percentage (0-100) of * $hex_a; the remainder is $hex_b. Returns a 6-digit hex (or $hex_a * unchanged if either input is not parseable). */ public static function mix_hex( string $hex_a, string $hex_b, int $weight_a ): string { $a = self::hex_to_rgb( $hex_a ); $b = self::hex_to_rgb( $hex_b ); if ( null === $a || null === $b ) { return $hex_a; } $w = max( 0, min( 100, $weight_a ) ) / 100; return sprintf( '#%02x%02x%02x', (int) round( $a[0] * $w + $b[0] * ( 1 - $w ) ), (int) round( $a[1] * $w + $b[1] * ( 1 - $w ) ), (int) round( $a[2] * $w + $b[2] * ( 1 - $w ) ) ); } /** * Pick a readable text colour (near-black or near-white) for content that * sits on $hex, using perceived (YIQ) luminance. */ public static function contrast_text( string $hex ): string { $rgb = self::hex_to_rgb( $hex ); if ( null === $rgb ) { return '#1d2327'; } $yiq = ( $rgb[0] * 299 + $rgb[1] * 587 + $rgb[2] * 114 ) / 1000; return $yiq >= 150 ? '#1d2327' : '#f0f0f1'; } /** Parse a 3- or 6-digit hex colour to an [r, g, b] array, or null. */ private static function hex_to_rgb( string $hex ): ?array { $hex = ltrim( trim( $hex ), '#' ); if ( 3 === strlen( $hex ) ) { $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2]; } if ( ! preg_match( '/^[A-Fa-f0-9]{6}$/', $hex ) ) { return null; } return [ hexdec( substr( $hex, 0, 2 ) ), hexdec( substr( $hex, 2, 2 ) ), hexdec( substr( $hex, 4, 2 ) ), ]; } public function render_page(): void { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'You do not have permission to access this page.', 'admin-buddy' ) ); } // phpcs:ignore WordPress.Security.NonceVerification.Recommended $active_tab = isset( $_GET['tab'] ) ? sanitize_key( $_GET['tab'] ) : 'adminui'; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput // Legacy tab slug redirects. $legacy_map = [ 'core' => 'adminui', 'dashboard' => 'adminui', 'notices' => 'adminui', 'advanced' => 'plugin-data', 'tools' => 'plugin-data', 'datatools' => 'plugin-data', 'setup' => 'modules', 'tweaks' => 'quick-settings' ]; if ( isset( $legacy_map[ $active_tab ] ) ) { $active_tab = $legacy_map[ $active_tab ]; // If redirected from tools, pre-select the tools subtab. if ( ! isset( $_GET['admbud_subtab'] ) && ( $_GET['tab'] ?? '' ) === 'tools' ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput,WordPress.Security.NonceVerification $_GET['admbud_subtab'] = 'tools'; // phpcs:ignore WordPress.Security.NonceVerification,WordPress.Security.ValidatedSanitizedInput } } $tabs = [ 'adminui' => [ 'label' => __( 'White Label', 'admin-buddy' ), 'icon' => '', ], 'colours' => [ 'label' => __( 'Colours', 'admin-buddy' ), 'icon' => '', ], 'login' => [ 'label' => __( 'Login', 'admin-buddy' ), 'icon' => '', ], 'maintenance' => [ 'label' => __( 'Maintenance', 'admin-buddy' ), 'icon' => '', ], 'smtp' => [ 'label' => __( 'SMTP', 'admin-buddy' ), 'icon' => '', ], 'roles' => [ 'label' => __( 'User Roles', 'admin-buddy' ), 'icon' => '', 'keys' => [ 'wp_user_roles' ], 'warn' => __( 'Importing roles will overwrite ALL role definitions. Use with care.', 'admin-buddy' ), ], 'modules' => [ 'group' => 'manage', 'label' => __( 'Modules', 'admin-buddy' ), 'icon' => '', ], 'plugin-data' => [ 'group' => 'manage', 'label' => __( 'Plugin Data', 'admin-buddy' ), 'icon' => '', ], 'quick-settings' => [ 'group' => 'manage', 'label' => __( 'Quick Settings', 'admin-buddy' ), 'icon' => '', ], ]; // Only show the License tab when the licensing SDK is present. if ( function_exists( 'admbud_fs' ) ) { $tabs['license'] = [ 'group' => 'standalone', 'label' => __( 'License', 'admin-buddy' ), 'icon' => '', ]; } if ( ! array_key_exists( $active_tab, $tabs ) ) $active_tab = 'adminui'; // Apply module visibility from Modules tab. // Standalone tabs (modules, plugin-data, license) and manage-group tabs (quick-settings, source) // are always kept; module tabs are filtered by enabled modules. $enabled_slugs = $this->get_enabled_tabs(); $always_keep = [ 'modules', 'plugin-data', 'license' ]; foreach ( array_keys( $tabs ) as $slug ) { if ( in_array( $slug, $always_keep, true ) ) { continue; } if ( ! in_array( $slug, $enabled_slugs, true ) ) { unset( $tabs[ $slug ] ); } } // Ensure active_tab still exists after filter. if ( ! array_key_exists( $active_tab, $tabs ) ) $active_tab = array_key_first( $tabs ); $maintenance_mode = admbud_get_option( 'admbud_maintenance_mode', 'off' ); // ab-loading hides the frame until JS confirms the correct tab - prevents flicker. $loading_class = empty( $_GET['tab'] ) ? ' ab-loading' : ''; // phpcs:ignore WordPress.Security.NonceVerification ?>
/

inside `.wrap`. Our chrome renders its title as an SVG * logo (no heading element), so without this marker the FS notice * was being injected after the Welcome banner's

, landing in * the middle of the banner. Emitting `.wp-header-end` here pins * all notices to the very top of the page, above the topbar. */ ?>
v
response (e.g. Hide Plugin/Theme Update Nag toggles). $admbud_updates_core = []; $admbud_updates_plugins = []; $admbud_updates_themes = []; if ( function_exists( 'get_core_updates' ) ) { // get_core_updates() uses its own option, not affected by transient filters. require_once ABSPATH . 'wp-admin/includes/update.php'; $core_updates = get_core_updates( [ 'dismissed' => false ] ); if ( is_array( $core_updates ) ) { foreach ( $core_updates as $cu ) { if ( isset( $cu->response ) && $cu->response === 'upgrade' ) { $admbud_updates_core[] = [ 'current' => get_bloginfo( 'version' ), 'new' => $cu->current ?? '', 'url' => admin_url( 'update-core.php' ), ]; } } } } // Read the plugin and theme update site-transients via the WordPress API. // If the transient is missing or stale (>12h), trigger a fresh check first. $raw_plugin_transient = get_site_transient( 'update_plugins' ); $raw_theme_transient = get_site_transient( 'update_themes' ); $stale_threshold = time() - ( 12 * HOUR_IN_SECONDS ); $plugins_stale = ! $raw_plugin_transient || ! is_object( $raw_plugin_transient ) || empty( $raw_plugin_transient->last_checked ) || $raw_plugin_transient->last_checked < $stale_threshold; $themes_stale = ! $raw_theme_transient || ! is_object( $raw_theme_transient ) || empty( $raw_theme_transient->last_checked ) || $raw_theme_transient->last_checked < $stale_threshold; if ( $plugins_stale || $themes_stale ) { require_once ABSPATH . WPINC . '/update.php'; if ( $plugins_stale ) { wp_update_plugins(); $raw_plugin_transient = get_site_transient( 'update_plugins' ); } if ( $themes_stale ) { wp_update_themes(); $raw_theme_transient = get_site_transient( 'update_themes' ); } } if ( $raw_plugin_transient && ! empty( $raw_plugin_transient->response ) ) { if ( ! function_exists( 'get_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $all_plugins = get_plugins(); foreach ( $raw_plugin_transient->response as $plugin_file => $plugin_data ) { if ( 0 !== validate_file( $plugin_file ) ) { continue; } $plugin_info = $all_plugins[ $plugin_file ] ?? []; $admbud_updates_plugins[] = [ 'name' => $plugin_info['Name'] ?? $plugin_file, 'current' => $plugin_info['Version'] ?? '', 'new' => $plugin_data->new_version ?? '', 'url' => admin_url( 'plugins.php' ), 'details' => $plugin_data->url ?? '', ]; } } // Themes. if ( $raw_theme_transient && ! empty( $raw_theme_transient->response ) ) { foreach ( $raw_theme_transient->response as $theme_slug => $theme_data ) { $theme = wp_get_theme( $theme_slug ); $admbud_updates_themes[] = [ 'name' => $theme->get( 'Name' ) ?: $theme_slug, 'current' => $theme->get( 'Version' ) ?: '', 'new' => $theme_data['new_version'] ?? '', 'url' => admin_url( 'themes.php' ), ]; } } $admbud_update_count = count( $admbud_updates_core ) + count( $admbud_updates_plugins ) + count( $admbud_updates_themes ); ?> __( 'Maintenance', 'admin-buddy' ), 'class' => 'ab-badge--danger', 'tab' => 'maintenance', 'title' => __( 'Maintenance Mode is active: visitors see a maintenance page', 'admin-buddy' ), 'icon' => '', ]; } // Coming Soon active if ( $admbud_maint_mode === 'coming_soon' ) { $status_indicators[] = [ 'label' => __( 'Coming Soon', 'admin-buddy' ), 'class' => 'ab-badge--info', 'tab' => 'maintenance', 'title' => __( 'Coming Soon mode is active: visitors see a coming soon page', 'admin-buddy' ), 'icon' => '', ]; } // Search Engine Visibility discouraged if ( get_option( 'blog_public' ) === '0' ) { $status_indicators[] = [ 'label' => __( 'Noindex', 'admin-buddy' ), 'class' => 'ab-badge--warning', 'tab' => '', 'title' => __( 'Search engines are discouraged from indexing this site (Settings → Reading)', 'admin-buddy' ), 'icon' => '', 'url' => admin_url( 'options-reading.php' ), ]; } // Checklist pill - always rendered. Opens the same // off-canvas panel that the WP admin bar pill opens, via // the data-ab-checklist-open hook in checklist-panel.js. // // Severity-aware rendering MUST mirror Checklist::add_bar_node() // exactly so the two pills never disagree. Count shown reflects // the worst tier only — critical first, warning only when no // critical exists — so "Checklist (3)" means the same thing // in both the WP admin bar and the AB topbar. $admbud_checklist_counts = [ 'critical' => 0, 'warning' => 0 ]; if ( class_exists( '\\Admbud\\Checklist' ) ) { $admbud_checklist_counts = \Admbud\Checklist::get_instance()->counts(); } if ( $admbud_checklist_counts['critical'] > 0 ) { $admbud_checklist_class = 'ab-badge--danger'; /* translators: %d: number of critical issues */ $admbud_checklist_label = sprintf( _n( 'Checklist (%d)', 'Checklist (%d)', $admbud_checklist_counts['critical'], 'admin-buddy' ), $admbud_checklist_counts['critical'] ); } elseif ( $admbud_checklist_counts['warning'] > 0 ) { $admbud_checklist_class = 'ab-badge--warning'; /* translators: %d: number of warnings */ $admbud_checklist_label = sprintf( _n( 'Checklist (%d)', 'Checklist (%d)', $admbud_checklist_counts['warning'], 'admin-buddy' ), $admbud_checklist_counts['warning'] ); } else { $admbud_checklist_class = 'ab-badge--success'; $admbud_checklist_label = __( 'Checklist', 'admin-buddy' ); } // Checkmark-in-square icon for the Checklist badge. $admbud_checklist_icon = ''; ?>
render_tab_adminui(); break; case 'colours': $this->render_tab_colours(); break; case 'login': $this->render_tab_login(); break; case 'maintenance': $this->render_tab_maintenance(); break; case 'smtp': $this->render_tab_smtp(); break; case 'roles': $this->render_tab_roles(); break; case 'modules': $this->render_tab_modules(); break; case 'plugin-data': $this->render_tab_plugin_data(); break; case 'quick-settings': $this->render_tab_quick_settings(); break; } ?>

'; } // ============================================================================ // TAB: COLOURS // ============================================================================ private function render_tab_colours(): void { $settings = $this; // passed to render file as $settings require ADMBUD_DIR . 'includes/render-tab-colours.php'; } // ============================================================================ // TAB: UI TWEAKS // ============================================================================ // TAB: UI TWEAKS // ============================================================================ private function render_tab_adminui(): void { $settings = $this; // passed to render file as $settings require ADMBUD_DIR . 'includes/render-tab-adminui.php'; } // ============================================================================ // ============================================================================ // TAB: LOGIN // ============================================================================ private function render_tab_login(): void { $settings = $this; // passed to render file as $settings require ADMBUD_DIR . 'includes/render-tab-login.php'; } // ============================================================================ // TAB: MAINTENANCE // ============================================================================ private function render_tab_maintenance(): void { $settings = $this; // passed to render file as $settings require ADMBUD_DIR . 'includes/render-tab-maintenance.php'; } // ============================================================================ // TAB: SMTP // ============================================================================ private function render_tab_smtp(): void { require_once ADMBUD_DIR . 'includes/render-tab-smtp.php'; } // ============================================================================ // TAB: USER ROLES // ============================================================================ private function render_tab_roles(): void { require_once ADMBUD_DIR . 'includes/render-tab-roles.php'; } /** * Returns the list of tabs that the user can toggle on/off in Setup. * 'modules' itself is always shown and is excluded from this list. */ private function get_manageable_tabs(): array { return [ // -- Core --------------------------------------------------------- 'adminui' => [ 'group' => 'interface', 'label' => __( 'White Label', 'admin-buddy' ), 'icon' => '' ], 'colours' => [ 'group' => 'interface', 'label' => __( 'Colours', 'admin-buddy' ), 'icon' => '' ], 'login' => [ 'group' => 'interface', 'label' => __( 'Login', 'admin-buddy' ), 'icon' => '' ], 'maintenance' => [ 'group' => 'interface', 'label' => __( 'Maintenance', 'admin-buddy' ), 'icon' => '' ], 'smtp' => [ 'group' => 'utilities', 'label' => __( 'SMTP', 'admin-buddy' ), 'icon' => '' ], // -- Utilities ---------------------------------------------------- 'roles' => [ 'group' => 'utilities', 'label' => __( 'User Roles', 'admin-buddy' ), 'icon' => '' ], // -- Manage ------------------------------------------------------- 'quick-settings' => [ 'group' => 'manage', 'label' => __( 'Quick Settings', 'admin-buddy' ), 'icon' => '' ], // Media Manager (FREE folder core): toggleable module with NO settings tab - // config lives in the folder panel on the Media Library (gear icon). 'url' // points the nav/admin-bar quick-link there instead of a non-existent &tab=. 'media-manager' => [ 'group' => 'utilities', 'label' => __( 'Media Manager', 'admin-buddy' ), 'url' => admin_url( 'upload.php' ), 'icon' => '' ], ]; } /** * Returns the set of enabled tab slugs. * Empty stored value = first launch = nothing enabled (only Setup tab shows). * After first save, we store the explicit list (may be empty string for "all off"). */ /** * AJAX handler - instantly saves the enabled-tab list when a Setup toggle changes. * Expects POST: nonce, slug (tab slug), enabled ('1' or '0'). */ public function ajax_setup_toggle(): void { check_ajax_referer( 'admbud_modules_toggle', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( [ 'message' => 'Unauthorized' ], 403 ); } $slug = sanitize_key( wp_unslash( $_POST['slug'] ?? '' ) ); $enabled = ( sanitize_text_field( wp_unslash( $_POST['enabled'] ?? '0' ) ) === '1' ); $valid = array_keys( $this->get_manageable_tabs() ); if ( ! in_array( $slug, $valid, true ) ) { wp_send_json_error( [ 'message' => 'Invalid slug' ], 400 ); } // Pro tier check: block Pro-only modules for unpaid users. // Free modules are always activatable regardless of license state. if ( $enabled ) { $allowed = function_exists( 'admbud_allowed_modules' ) ? admbud_allowed_modules() : $valid; if ( ! in_array( $slug, $allowed, true ) ) { wp_send_json_error( [ 'message' => __( 'This module requires a Pro license.', 'admin-buddy' ), 'reason' => 'pro_required' ], 403 ); } } // Fetch current list, toggle the slug, save. $current = $this->get_enabled_tabs(); if ( $enabled ) { if ( ! in_array( $slug, $current, true ) ) { $current[] = $slug; } } else { $current = array_values( array_filter( $current, fn( $s ) => $s !== $slug ) ); } // Store as comma-separated string. Empty string = nothing enabled. admbud_update_option( 'admbud_modules_enabled_tabs', implode( ',', $current ) ); wp_send_json_success( [ 'enabled_tabs' => $current ] ); } /** * AJAX handler - enable/disable all modules in a group atomically (single read/write). */ public function ajax_setup_group_toggle(): void { check_ajax_referer( 'admbud_modules_toggle', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( [ 'message' => 'Unauthorized' ], 403 ); } $group = sanitize_key( wp_unslash( $_POST['group'] ?? '' ) ); $enabled = ( sanitize_text_field( wp_unslash( $_POST['enabled'] ?? '0' ) ) === '1' ); $all = $this->get_manageable_tabs(); $allowed = function_exists( 'admbud_allowed_modules' ) ? admbud_allowed_modules() : array_keys( $all ); $group_slugs = array_keys( array_filter( $all, function ( $tab ) use ( $group ) { return ( $tab['group'] ?? '' ) === $group; } ) ); if ( empty( $group_slugs ) ) { wp_send_json_error( [ 'message' => 'Unknown group' ], 400 ); } $current = $this->get_enabled_tabs(); foreach ( $group_slugs as $slug ) { // Skip Pro modules for free tier when enabling. if ( $enabled && ! in_array( $slug, $allowed, true ) ) { continue; } if ( $enabled ) { if ( ! in_array( $slug, $current, true ) ) { $current[] = $slug; } } else { $current = array_values( array_filter( $current, fn( $s ) => $s !== $slug ) ); } } admbud_update_option( 'admbud_modules_enabled_tabs', implode( ',', $current ) ); wp_send_json_success( [ 'enabled_tabs' => $current ] ); } /** * AJAX handler - bulk enable/disable all modules at once. * Expects POST: nonce, enabled ('1' or '0'). */ public function ajax_setup_bulk_toggle(): void { check_ajax_referer( 'admbud_modules_toggle', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( [ 'message' => 'Unauthorized' ], 403 ); } $enabled = ( sanitize_text_field( wp_unslash( $_POST['enabled'] ?? '0' ) ) === '1' ); $valid = array_keys( $this->get_manageable_tabs() ); $allowed = function_exists( 'admbud_allowed_modules' ) ? admbud_allowed_modules() : $valid; // Enable All only enables allowed modules (free users skip Pro modules). $new_list = $enabled ? $allowed : []; admbud_update_option( 'admbud_modules_enabled_tabs', implode( ',', $new_list ) ); wp_send_json_success( [ 'enabled_tabs' => $new_list ] ); } /** * AJAX: toggle a single Quick Setting on or off. * POST: nonce, key (option key), value ('1' or '0'). */ public function ajax_qs_toggle(): void { check_ajax_referer( 'admbud_qs_nonce', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( [ 'message' => 'Unauthorized' ], 403 ); } $valid_keys = [ 'admbud_qs_disable_emoji', 'admbud_qs_disable_jquery_migrate', 'admbud_qs_remove_feed_links', 'admbud_qs_remove_rsd', 'admbud_qs_remove_wlw', 'admbud_qs_remove_shortlink', 'admbud_qs_remove_restapi_link', 'admbud_qs_disable_embeds', 'admbud_qs_remove_version', 'admbud_qs_disable_xmlrpc', 'admbud_qs_disable_rest_api', 'admbud_qs_disable_file_edit', 'admbud_qs_disable_feeds', 'admbud_qs_disable_self_ping', 'admbud_qs_disable_comments_default', 'admbud_qs_duplicate_post', 'admbud_qs_user_last_seen', 'admbud_qs_allow_svg', 'admbud_qs_hide_adminbar_frontend', 'admbud_qs_hide_adminbar_backend', 'admbud_qs_collapse_menu', 'admbud_qs_hide_adminbar_checklist', 'admbud_qs_hide_adminbar_noindex', 'admbud_qs_sidebar_user_menu', 'admbud_notices_suppress', ]; $key = sanitize_key( wp_unslash( $_POST['key'] ?? '' ) ); $value = ( sanitize_text_field( wp_unslash( $_POST['value'] ?? '0' ) ) === '1' ) ? '1' : '0'; if ( ! in_array( $key, $valid_keys, true ) ) { wp_send_json_error( [ 'message' => 'Invalid key' ], 400 ); } // When Hide Admin Bar (Backend) flips ON and the sidebar user menu is // currently OFF, auto-enable it so the user keeps a way to navigate // (the sidebar user menu is also where the Visit Site link lives when // the admin bar is hidden). Role list is copied so the same audience // who lose the admin bar gain the replacement nav. The user can still // toggle it off afterwards — we don't track dissent. $auto_enabled_user_menu = false; if ( $key === 'admbud_qs_hide_adminbar_backend' && $value === '1' && get_option( 'admbud_qs_sidebar_user_menu', '0' ) === '0' ) { $copy_roles = (string) get_option( 'admbud_qs_hide_adminbar_backend_roles', 'administrator' ); update_option( 'admbud_qs_sidebar_user_menu', '1' ); update_option( 'admbud_qs_sidebar_user_menu_roles', $copy_roles ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- admbud_ is the plugin prefix. do_action( 'admbud_qs_toggled', 'admbud_qs_sidebar_user_menu', '1' ); $auto_enabled_user_menu = true; } update_option( $key, $value ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- admbud_ is the plugin prefix. do_action( 'admbud_qs_toggled', $key, $value ); wp_send_json_success( [ 'key' => $key, 'value' => $value, 'auto_enabled' => $auto_enabled_user_menu ? 'admbud_qs_sidebar_user_menu' : null, ] ); } /** * AJAX: set all Quick Settings to on or off at once. * POST: nonce, value ('1' or '0'). */ public function ajax_qs_bulk(): void { check_ajax_referer( 'admbud_qs_nonce', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( [ 'message' => 'Unauthorized' ], 403 ); } $value = ( sanitize_text_field( wp_unslash( $_POST['value'] ?? '0' ) ) === '1' ) ? '1' : '0'; $keys = [ 'admbud_qs_disable_emoji', 'admbud_qs_disable_jquery_migrate', 'admbud_qs_remove_feed_links', 'admbud_qs_remove_rsd', 'admbud_qs_remove_wlw', 'admbud_qs_remove_shortlink', 'admbud_qs_remove_restapi_link', 'admbud_qs_disable_embeds', 'admbud_qs_remove_version', 'admbud_qs_disable_xmlrpc', 'admbud_qs_disable_rest_api', 'admbud_qs_disable_file_edit', 'admbud_qs_disable_feeds', 'admbud_qs_disable_self_ping', 'admbud_qs_disable_comments_default', 'admbud_qs_duplicate_post', 'admbud_qs_user_last_seen', 'admbud_qs_allow_svg', 'admbud_notices_suppress', ]; foreach ( $keys as $key ) { update_option( $key, $value ); } // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- admbud_ is the plugin prefix. do_action( 'admbud_qs_bulk_toggled', $value ); wp_send_json_success( [ 'value' => $value ] ); } /** * AJAX: save SVG upload allowed roles. * POST: nonce, roles (comma-separated role slugs). */ /** * AJAX: save roles for any Quick Settings toggle that has per-role control. * POST: nonce, key (option key), roles (comma-separated role slugs). */ public function ajax_qs_save_roles(): void { check_ajax_referer( 'admbud_qs_nonce', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( [ 'message' => 'Unauthorized' ], 403 ); } // Whitelist of keys that support per-role control. $valid_keys = [ 'admbud_qs_allow_svg', 'admbud_qs_hide_adminbar_frontend', 'admbud_qs_hide_adminbar_backend', 'admbud_qs_hide_adminbar_checklist', 'admbud_qs_hide_adminbar_noindex', 'admbud_qs_collapse_menu', 'admbud_qs_sidebar_user_menu', ]; $key = sanitize_key( wp_unslash( $_POST['key'] ?? '' ) ); // Backward compat: old admbud_qs_svg_roles calls don't send key. if ( empty( $key ) ) { $key = 'admbud_qs_allow_svg'; } if ( ! in_array( $key, $valid_keys, true ) ) { wp_send_json_error( [ 'message' => 'Invalid key' ], 400 ); } $raw = sanitize_text_field( wp_unslash( $_POST['roles'] ?? '' ) ); $roles = array_filter( array_map( 'sanitize_key', explode( ',', $raw ) ) ); // Validate against actual registered roles. $valid_roles = array_keys( wp_roles()->roles ); $roles = array_values( array_intersect( $roles, $valid_roles ) ); update_option( $key . '_roles', implode( ',', $roles ) ); wp_send_json_success( [ 'roles' => $roles ] ); } public function ajax_apply_preset(): void { check_ajax_referer( 'admbud_apply_preset', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( [ 'message' => 'Unauthorized' ], 403 ); } $slug = sanitize_key( wp_unslash( $_POST['preset'] ?? '' ) ); $presets = $this->get_colour_presets(); if ( ! isset( $presets[ $slug ] ) ) { wp_send_json_error( [ 'message' => 'Unknown preset' ], 400 ); } $allowed_prefixes = [ 'admbud_colours_', 'admbud_login_bg', 'admbud_login_grad', 'admbud_cs_', 'admbud_maint_' ]; foreach ( $presets[ $slug ]['values'] as $key => $val ) { $ok = false; foreach ( $allowed_prefixes as $pfx ) { if ( strpos( $key, $pfx ) === 0 ) { $ok = true; break; } } if ( $ok ) { update_option( $key, sanitize_text_field( $val ) ); } } $this->clear_bricks_overrides(); // Ensure Admin Bar Flyout Bg always matches Admin Bar Bg. $adminbar_bg = admbud_get_option( 'admbud_colours_adminbar_bg', '' ); if ( $adminbar_bg ) { admbud_update_option( 'admbud_colours_adminbar_submenu_bg', $adminbar_bg ); } $this->sync_login_card_to_page_bg(); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- admbud_ is the plugin prefix. do_action( 'admbud_colour_preset_applied', $slug, $presets[ $slug ]['label'] ?? $slug ); wp_send_json_success( [ 'preset' => $slug ] ); } /** * Clear Bricks builder colour overrides so they re-derive from the new palette. * Called after preset or palette application. */ private function clear_bricks_overrides(): void { $bricks_keys = [ 'admbud_bricks_builder_bg', 'admbud_bricks_builder_bg_2', 'admbud_bricks_builder_bg_3', 'admbud_bricks_builder_bg_accent', 'admbud_bricks_builder_color', 'admbud_bricks_builder_color_description', 'admbud_bricks_builder_color_accent', 'admbud_bricks_builder_color_accent_inverse', 'admbud_bricks_builder_color_knob', 'admbud_bricks_builder_border_color', 'admbud_bricks_bricks_tooltip_bg', 'admbud_bricks_bricks_tooltip_text', ]; foreach ( $bricks_keys as $key ) { delete_option( $key ); } } /** * Adapt the login form (card) background to a freshly-applied login page * background. Called after a preset or palette is applied so the card no * longer stays stark white on a dark scheme - it tracks the page tone: * - light page background -> keep the card white (WP-like, high contrast). * - dark page background -> a subtly elevated surface (10% toward white) * so the card reads as a raised panel instead of blending into the page. * Card text is forced back to auto-contrast so it stays readable on the new * surface. This only seeds a sensible starting colour - the user can still * tweak admbud_login_card_bg_color / _text afterward on the Login tab. */ private function sync_login_card_to_page_bg(): void { $page_bg = sanitize_text_field( admbud_get_option( 'admbud_login_bg_color', '' ) ); if ( ! $page_bg || ! preg_match( '/^#?[A-Fa-f0-9]{3}([A-Fa-f0-9]{3})?$/', $page_bg ) ) { return; } $card = ( self::contrast_text( $page_bg ) === '#1d2327' ) ? '#ffffff' // light page -> white card : self::mix_hex( '#ffffff', $page_bg, 10 ); // dark page -> elevated surface admbud_update_option( 'admbud_login_card_bg_color', $card ); admbud_update_option( 'admbud_login_card_text_auto', '1' ); } /** * AJAX: Apply generated palette values directly to the database. * Used by the palette generator to save login/maintenance/coming-soon * colours that don't exist on the Colours form. */ public function ajax_apply_palette(): void { check_ajax_referer( 'admbud_apply_preset', 'nonce' ); if ( ! current_user_can( 'manage_options' ) ) { wp_send_json_error( [ 'message' => 'Unauthorized' ], 403 ); } $raw = wp_unslash( $_POST['values'] ?? '' ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput // json_decode does not sanitise; each decoded key/value pair is run // through sanitize_key() / sanitize_text_field() below, AND only keys // matching the admbud_ prefix whitelist are written to options. $data = json_decode( $raw, true ); if ( ! is_array( $data ) ) { wp_send_json_error( [ 'message' => 'Invalid data' ], 400 ); } $allowed_prefixes = [ 'admbud_colours_', 'admbud_login_', 'admbud_cs_', 'admbud_maint_' ]; $count = 0; foreach ( $data as $key => $val ) { $ok = false; foreach ( $allowed_prefixes as $pfx ) { if ( strpos( $key, $pfx ) === 0 ) { $ok = true; break; } } if ( $ok ) { update_option( sanitize_key( $key ), sanitize_text_field( $val ) ); $count++; } } $this->clear_bricks_overrides(); // Ensure Admin Bar Flyout Bg always matches Admin Bar Bg. $adminbar_bg = admbud_get_option( 'admbud_colours_adminbar_bg', '' ); if ( $adminbar_bg ) { admbud_update_option( 'admbud_colours_adminbar_submenu_bg', $adminbar_bg ); } $this->sync_login_card_to_page_bg(); Colours::maybe_bust_cache(); wp_send_json_success( [ 'count' => $count ] ); } public function get_colour_presets(): array { return [ 'violety' => [ 'label' => 'Violety', 'description' => 'Deep violet sidebar with rich purple gradient. The default Admin Buddy palette.', 'swatches' => [ '#7c3aed', '#6d28d9', '#1e1b2e', '#2e1065', '#ede9fe' ], 'values' => [ 'admbud_colours_primary' => '#7c3aed', 'admbud_colours_secondary' => '#6d28d9', 'admbud_colours_hover_bg' => '#6d28d9', 'admbud_colours_active_bg' => '#7c3aed', 'admbud_colours_menu_text' => '#ede9fe', 'admbud_colours_menu_bg' => '#1e1b2e', 'admbud_colours_active_text' => '#ffffff', 'admbud_colours_sep_color' => '#5600ed', 'admbud_colours_body_bg' => '#ffffff', 'admbud_colours_menu_item_sep' => '1', 'admbud_colours_sidebar_gradient' => '1', 'admbud_colours_sidebar_grad_dir' => 'to top', 'admbud_colours_sidebar_grad_from' => '#2e1065', 'admbud_colours_sidebar_grad_to' => '#1e1b2e', 'admbud_colours_submenu_text' => '#ede9fe', 'admbud_colours_hover_text' => '#ede9fe', 'admbud_colours_active_parent_text' => '#ffffff', 'admbud_colours_submenu_bg' => '#7c3aed', 'admbud_colours_submenu_hover_bg' => '#7c3aed', 'admbud_colours_submenu_hover_text' => '#ede9fe', 'admbud_colours_submenu_active_bg' => '#7c3aed', 'admbud_colours_submenu_active_text' => '#ede9fe', 'admbud_colours_adminbar_bg' => '#1a1828', 'admbud_colours_adminbar_text' => '#d4d1e0', 'admbud_colours_adminbar_hover_bg' => '#7c3aed', 'admbud_colours_adminbar_submenu_bg' => '#1a1828', 'admbud_colours_adminbar_hover_text' => '#ffffff', 'admbud_colours_adminbar_sub_text' => '#d4d1e0', 'admbud_colours_adminbar_sub_hover_bg' => '', 'admbud_colours_adminbar_sub_hover_text' => '#ffffff', 'admbud_colours_shadow_colour' => '', 'admbud_colours_pill_maintenance' => '#dd3333', 'admbud_colours_pill_coming_soon' => '#dd3333', 'admbud_colours_pill_noindex' => '#dd9933', 'admbud_colours_pill_admin_buddy' => '#7c3aed', // Login page 'admbud_login_bg_type' => 'solid', 'admbud_login_bg_color' => '#1e1b2e', 'admbud_login_grad_from' => '#2e1065', 'admbud_login_grad_to' => '#1e1b2e', 'admbud_login_grad_direction' => 'to bottom right', // Coming Soon page 'admbud_cs_bg_type' => 'gradient', 'admbud_cs_bg_color' => '#1e1b2e', 'admbud_cs_grad_from' => '#2e1065', 'admbud_cs_grad_to' => '#1e1b2e', 'admbud_cs_grad_direction' => 'to bottom right', 'admbud_cs_text_color' => '#ede9fe', 'admbud_cs_message_color' => '#c4b5fd', // Maintenance page 'admbud_maint_bg_type' => 'gradient', 'admbud_maint_bg_color' => '#1e1b2e', 'admbud_maint_grad_from' => '#2e1065', 'admbud_maint_grad_to' => '#1e1b2e', 'admbud_maint_grad_direction' => 'to bottom right', 'admbud_maint_text_color' => '#ede9fe', 'admbud_maint_message_color' => '#c4b5fd', // Content area 'admbud_colours_content_heading' => '#1d2327', 'admbud_colours_content_text' => '#3c434a', 'admbud_colours_content_link' => '#7c3aed', 'admbud_colours_content_link_hover' => '#6d28d9', 'admbud_colours_table_header_bg' => '#f4effd', 'admbud_colours_table_header_text' => '#1d2327', 'admbud_colours_table_header_link' => '#1d2327', 'admbud_colours_table_row_bg' => '#ffffff', 'admbud_colours_table_row_text' => '#3c434a', 'admbud_colours_table_row_alt_bg' => '#f9f7fe', 'admbud_colours_table_row_alt_text' => '#3c434a', 'admbud_colours_table_row_hover' => '#efe7fc', 'admbud_colours_table_border' => '#decdfa', 'admbud_colours_table_row_separator' => '#ebe1fc', 'admbud_colours_table_title_link' => '#7c3aed', 'admbud_colours_table_action_link' => '#7c3aed', 'admbud_colours_input_bg' => '#ffffff', 'admbud_colours_input_border' => '#d1baf8', 'admbud_colours_input_focus' => '#7c3aed', 'admbud_colours_btn_primary_bg' => '#7c3aed', 'admbud_colours_btn_primary_text' => '#ffffff', 'admbud_colours_btn_primary_hover' => '#6d28d9', 'admbud_colours_btn_secondary_bg' => '#f7f3fd', 'admbud_colours_postbox_bg' => '#ffffff', 'admbud_colours_postbox_header_bg' => '#f9f7fe', 'admbud_colours_postbox_border' => '#decdfa', 'admbud_colours_notice_bg' => '#fbf9fe', ], ], 'tealy' => [ 'label' => 'Tealy', 'description' => 'Fresh teal sidebar with deep ocean gradient.', 'swatches' => [ '#0d9488', '#0f766e', '#0d1e1e', '#042f2e', '#ccfbf1' ], 'values' => [ 'admbud_colours_primary' => '#0d9488', 'admbud_colours_secondary' => '#0f766e', 'admbud_colours_hover_bg' => '#0f766e', 'admbud_colours_active_bg' => '#0d9488', 'admbud_colours_menu_text' => '#ccfbf1', 'admbud_colours_menu_bg' => '#0d1e1e', 'admbud_colours_active_text' => '#ffffff', 'admbud_colours_sep_color' => '#0d9488', 'admbud_colours_body_bg' => '#ffffff', 'admbud_colours_menu_item_sep' => '1', 'admbud_colours_sidebar_gradient' => '1', 'admbud_colours_sidebar_grad_dir' => 'to top', 'admbud_colours_sidebar_grad_from' => '#042f2e', 'admbud_colours_sidebar_grad_to' => '#0d1e1e', 'admbud_colours_submenu_text' => '#ccfbf1', 'admbud_colours_hover_text' => '#ccfbf1', 'admbud_colours_active_parent_text' => '#ffffff', 'admbud_colours_submenu_bg' => '#0d9488', 'admbud_colours_submenu_hover_bg' => '#0d9488', 'admbud_colours_submenu_hover_text' => '#ccfbf1', 'admbud_colours_submenu_active_bg' => '#0d9488', 'admbud_colours_submenu_active_text' => '#ccfbf1', 'admbud_colours_adminbar_bg' => '#0b1a1a', 'admbud_colours_adminbar_text' => '#bbded7', 'admbud_colours_adminbar_hover_bg' => '#0d9488', 'admbud_colours_adminbar_submenu_bg' => '#0b1a1a', 'admbud_colours_adminbar_hover_text' => '#ffffff', 'admbud_colours_adminbar_sub_text' => '#bbded7', 'admbud_colours_adminbar_sub_hover_bg' => '', 'admbud_colours_adminbar_sub_hover_text' => '#ffffff', 'admbud_colours_shadow_colour' => '', 'admbud_colours_pill_maintenance' => '#dd3333', 'admbud_colours_pill_coming_soon' => '#dd3333', 'admbud_colours_pill_noindex' => '#dd9933', 'admbud_colours_pill_admin_buddy' => '#0d9488', // Login page 'admbud_login_bg_type' => 'solid', 'admbud_login_bg_color' => '#0d1e1e', 'admbud_login_grad_from' => '#042f2e', 'admbud_login_grad_to' => '#0d1e1e', 'admbud_login_grad_direction' => 'to bottom right', // Coming Soon page 'admbud_cs_bg_type' => 'gradient', 'admbud_cs_bg_color' => '#0d1e1e', 'admbud_cs_grad_from' => '#042f2e', 'admbud_cs_grad_to' => '#0d1e1e', 'admbud_cs_grad_direction' => 'to bottom right', 'admbud_cs_text_color' => '#ccfbf1', 'admbud_cs_message_color' => '#99f6e4', // Maintenance page 'admbud_maint_bg_type' => 'gradient', 'admbud_maint_bg_color' => '#0d1e1e', 'admbud_maint_grad_from' => '#042f2e', 'admbud_maint_grad_to' => '#0d1e1e', 'admbud_maint_grad_direction' => 'to bottom right', 'admbud_maint_text_color' => '#ccfbf1', 'admbud_maint_message_color' => '#99f6e4', // Content area 'admbud_colours_content_heading' => '#1d2327', 'admbud_colours_content_text' => '#3c434a', 'admbud_colours_content_link' => '#0d9488', 'admbud_colours_content_link_hover' => '#0f766e', 'admbud_colours_table_header_bg' => '#ebf6f5', 'admbud_colours_table_header_text' => '#1d2327', 'admbud_colours_table_header_link' => '#1d2327', 'admbud_colours_table_row_bg' => '#ffffff', 'admbud_colours_table_row_text' => '#3c434a', 'admbud_colours_table_row_alt_bg' => '#f5fafa', 'admbud_colours_table_row_alt_text' => '#3c434a', 'admbud_colours_table_row_hover' => '#e1f2f0', 'admbud_colours_table_border' => '#c2e4e1', 'admbud_colours_table_row_separator' => '#daeeed', 'admbud_colours_table_title_link' => '#0d9488', 'admbud_colours_table_action_link' => '#0d9488', 'admbud_colours_input_bg' => '#ffffff', 'admbud_colours_input_border' => '#aad9d5', 'admbud_colours_input_focus' => '#0d9488', 'admbud_colours_btn_primary_bg' => '#0d9488', 'admbud_colours_btn_primary_text' => '#ffffff', 'admbud_colours_btn_primary_hover' => '#0f766e', 'admbud_colours_btn_secondary_bg' => '#f0f8f7', 'admbud_colours_postbox_bg' => '#ffffff', 'admbud_colours_postbox_header_bg' => '#f5fafa', 'admbud_colours_postbox_border' => '#c2e4e1', 'admbud_colours_notice_bg' => '#f7fbfb', ], ], 'rosy' => [ 'label' => 'Rosy', 'description' => 'Bold rose-crimson sidebar with deep warm gradient.', 'swatches' => [ '#e11d48', '#be123c', '#1e0d14', '#4c0519', '#ffe4e6' ], 'values' => [ 'admbud_colours_primary' => '#e11d48', 'admbud_colours_secondary' => '#be123c', 'admbud_colours_hover_bg' => '#be123c', 'admbud_colours_active_bg' => '#e11d48', 'admbud_colours_menu_text' => '#ffe4e6', 'admbud_colours_menu_bg' => '#1e0d14', 'admbud_colours_active_text' => '#ffffff', 'admbud_colours_sep_color' => '#e11d48', 'admbud_colours_body_bg' => '#ffffff', 'admbud_colours_menu_item_sep' => '1', 'admbud_colours_sidebar_gradient' => '1', 'admbud_colours_sidebar_grad_dir' => 'to top', 'admbud_colours_sidebar_grad_from' => '#4c0519', 'admbud_colours_sidebar_grad_to' => '#1e0d14', 'admbud_colours_submenu_text' => '#ffe4e6', 'admbud_colours_hover_text' => '#ffe4e6', 'admbud_colours_active_parent_text' => '#ffffff', 'admbud_colours_submenu_bg' => '#e11d48', 'admbud_colours_submenu_hover_bg' => '#e11d48', 'admbud_colours_submenu_hover_text' => '#ffe4e6', 'admbud_colours_submenu_active_bg' => '#e11d48', 'admbud_colours_submenu_active_text' => '#ffe4e6', 'admbud_colours_adminbar_bg' => '#1a0b12', 'admbud_colours_adminbar_text' => '#e1cdce', 'admbud_colours_adminbar_hover_bg' => '#e11d48', 'admbud_colours_adminbar_submenu_bg' => '#1a0b12', 'admbud_colours_adminbar_hover_text' => '#ffffff', 'admbud_colours_adminbar_sub_text' => '#e1cdce', 'admbud_colours_adminbar_sub_hover_bg' => '', 'admbud_colours_adminbar_sub_hover_text' => '#ffffff', 'admbud_colours_shadow_colour' => '', 'admbud_colours_pill_maintenance' => '#dd3333', 'admbud_colours_pill_coming_soon' => '#dd3333', 'admbud_colours_pill_noindex' => '#dd9933', 'admbud_colours_pill_admin_buddy' => '#e11d48', // Login page 'admbud_login_bg_type' => 'solid', 'admbud_login_bg_color' => '#1e0d14', 'admbud_login_grad_from' => '#4c0519', 'admbud_login_grad_to' => '#1e0d14', 'admbud_login_grad_direction' => 'to bottom right', // Coming Soon page 'admbud_cs_bg_type' => 'gradient', 'admbud_cs_bg_color' => '#1e0d14', 'admbud_cs_grad_from' => '#4c0519', 'admbud_cs_grad_to' => '#1e0d14', 'admbud_cs_grad_direction' => 'to bottom right', 'admbud_cs_text_color' => '#ffe4e6', 'admbud_cs_message_color' => '#fecdd3', // Maintenance page 'admbud_maint_bg_type' => 'gradient', 'admbud_maint_bg_color' => '#1e0d14', 'admbud_maint_grad_from' => '#4c0519', 'admbud_maint_grad_to' => '#1e0d14', 'admbud_maint_grad_direction' => 'to bottom right', 'admbud_maint_text_color' => '#ffe4e6', 'admbud_maint_message_color' => '#fecdd3', // Content area 'admbud_colours_content_heading' => '#1d2327', 'admbud_colours_content_text' => '#3c434a', 'admbud_colours_content_link' => '#e11d48', 'admbud_colours_content_link_hover' => '#be123c', 'admbud_colours_table_header_bg' => '#fcecf0', 'admbud_colours_table_header_text' => '#1d2327', 'admbud_colours_table_header_link' => '#1d2327', 'admbud_colours_table_row_bg' => '#ffffff', 'admbud_colours_table_row_text' => '#3c434a', 'admbud_colours_table_row_alt_bg' => '#fdf5f7', 'admbud_colours_table_row_alt_text' => '#3c434a', 'admbud_colours_table_row_hover' => '#fbe3e9', 'admbud_colours_table_border' => '#f7c6d1', 'admbud_colours_table_row_separator' => '#fadde3', 'admbud_colours_table_title_link' => '#e11d48', 'admbud_colours_table_action_link' => '#e11d48', 'admbud_colours_input_bg' => '#ffffff', 'admbud_colours_input_border' => '#f4afbe', 'admbud_colours_input_focus' => '#e11d48', 'admbud_colours_btn_primary_bg' => '#e11d48', 'admbud_colours_btn_primary_text' => '#ffffff', 'admbud_colours_btn_primary_hover' => '#be123c', 'admbud_colours_btn_secondary_bg' => '#fdf1f4', 'admbud_colours_postbox_bg' => '#ffffff', 'admbud_colours_postbox_header_bg' => '#fdf5f7', 'admbud_colours_postbox_border' => '#f7c6d1', 'admbud_colours_notice_bg' => '#fef8f9', ], ], 'navy' => [ 'label' => 'Navy', 'description' => 'Classic navy-blue sidebar with deep ocean gradient. Professional and timeless.', 'swatches' => [ '#2563eb', '#1d4ed8', '#0f172a', '#1e3a5f', '#dbeafe' ], 'values' => [ 'admbud_colours_primary' => '#2563eb', 'admbud_colours_secondary' => '#1d4ed8', 'admbud_colours_hover_bg' => '#1d4ed8', 'admbud_colours_active_bg' => '#2563eb', 'admbud_colours_menu_text' => '#dbeafe', 'admbud_colours_menu_bg' => '#0f172a', 'admbud_colours_active_text' => '#ffffff', 'admbud_colours_sep_color' => '#2563eb', 'admbud_colours_body_bg' => '#ffffff', 'admbud_colours_menu_item_sep' => '1', 'admbud_colours_sidebar_gradient' => '1', 'admbud_colours_sidebar_grad_dir' => 'to top', 'admbud_colours_sidebar_grad_from' => '#1e3a5f', 'admbud_colours_sidebar_grad_to' => '#0f172a', 'admbud_colours_submenu_text' => '#dbeafe', 'admbud_colours_hover_text' => '#dbeafe', 'admbud_colours_active_parent_text' => '#ffffff', 'admbud_colours_submenu_bg' => '#2563eb', 'admbud_colours_submenu_hover_bg' => '#2563eb', 'admbud_colours_submenu_hover_text' => '#dbeafe', 'admbud_colours_submenu_active_bg' => '#2563eb', 'admbud_colours_submenu_active_text' => '#dbeafe', 'admbud_colours_adminbar_bg' => '#0d1425', 'admbud_colours_adminbar_text' => '#c6d2e0', 'admbud_colours_adminbar_hover_bg' => '#2563eb', 'admbud_colours_adminbar_submenu_bg' => '#0d1425', 'admbud_colours_adminbar_hover_text' => '#ffffff', 'admbud_colours_adminbar_sub_text' => '#c6d2e0', 'admbud_colours_adminbar_sub_hover_bg' => '', 'admbud_colours_adminbar_sub_hover_text' => '#ffffff', 'admbud_colours_shadow_colour' => '', 'admbud_colours_pill_maintenance' => '#dd3333', 'admbud_colours_pill_coming_soon' => '#dd3333', 'admbud_colours_pill_noindex' => '#dd9933', 'admbud_colours_pill_admin_buddy' => '#2563eb', // Login page 'admbud_login_bg_type' => 'solid', 'admbud_login_bg_color' => '#0f172a', 'admbud_login_grad_from' => '#1e3a5f', 'admbud_login_grad_to' => '#0f172a', 'admbud_login_grad_direction' => 'to bottom right', // Coming Soon page 'admbud_cs_bg_type' => 'gradient', 'admbud_cs_bg_color' => '#0f172a', 'admbud_cs_grad_from' => '#1e3a5f', 'admbud_cs_grad_to' => '#0f172a', 'admbud_cs_grad_direction' => 'to bottom right', 'admbud_cs_text_color' => '#dbeafe', 'admbud_cs_message_color' => '#93c5fd', // Maintenance page 'admbud_maint_bg_type' => 'gradient', 'admbud_maint_bg_color' => '#0f172a', 'admbud_maint_grad_from' => '#1e3a5f', 'admbud_maint_grad_to' => '#0f172a', 'admbud_maint_grad_direction' => 'to bottom right', 'admbud_maint_text_color' => '#dbeafe', 'admbud_maint_message_color' => '#93c5fd', // Content area 'admbud_colours_content_heading' => '#1d2327', 'admbud_colours_content_text' => '#3c434a', 'admbud_colours_content_link' => '#2563eb', 'admbud_colours_content_link_hover' => '#1d4ed8', 'admbud_colours_table_header_bg' => '#edf2fd', 'admbud_colours_table_header_text' => '#1d2327', 'admbud_colours_table_header_link' => '#1d2327', 'admbud_colours_table_row_bg' => '#ffffff', 'admbud_colours_table_row_text' => '#3c434a', 'admbud_colours_table_row_alt_bg' => '#f6f8fe', 'admbud_colours_table_row_alt_text' => '#3c434a', 'admbud_colours_table_row_hover' => '#e4ecfc', 'admbud_colours_table_border' => '#c8d8fa', 'admbud_colours_table_row_separator' => '#dee7fc', 'admbud_colours_table_title_link' => '#2563eb', 'admbud_colours_table_action_link' => '#2563eb', 'admbud_colours_input_bg' => '#ffffff', 'admbud_colours_input_border' => '#b2c8f8', 'admbud_colours_input_focus' => '#2563eb', 'admbud_colours_btn_primary_bg' => '#2563eb', 'admbud_colours_btn_primary_text' => '#ffffff', 'admbud_colours_btn_primary_hover' => '#1d4ed8', 'admbud_colours_btn_secondary_bg' => '#f1f5fd', 'admbud_colours_postbox_bg' => '#ffffff', 'admbud_colours_postbox_header_bg' => '#f6f8fe', 'admbud_colours_postbox_border' => '#c8d8fa', 'admbud_colours_notice_bg' => '#f8fafe', ], ], 'blacky' => [ 'label' => 'Blacky', 'description' => 'Dark charcoal sidebar with deep gray gradient. Sleek and minimal.', 'swatches' => [ '#555555', '#444444', '#1a1a1a', '#2a2a2a', '#e5e5e5' ], 'values' => [ 'admbud_colours_primary' => '#555555', 'admbud_colours_secondary' => '#444444', 'admbud_colours_hover_bg' => '#444444', 'admbud_colours_active_bg' => '#555555', 'admbud_colours_menu_text' => '#e5e5e5', 'admbud_colours_menu_bg' => '#1a1a1a', 'admbud_colours_active_text' => '#ffffff', 'admbud_colours_sep_color' => '#555555', 'admbud_colours_body_bg' => '#ffffff', 'admbud_colours_menu_item_sep' => '1', 'admbud_colours_sidebar_gradient' => '1', 'admbud_colours_sidebar_grad_dir' => 'to top', 'admbud_colours_sidebar_grad_from' => '#2a2a2a', 'admbud_colours_sidebar_grad_to' => '#1a1a1a', 'admbud_colours_submenu_text' => '#d0d0d0', 'admbud_colours_hover_text' => '#e5e5e5', 'admbud_colours_active_parent_text' => '#ffffff', 'admbud_colours_submenu_bg' => '#555555', 'admbud_colours_submenu_hover_bg' => '#555555', 'admbud_colours_submenu_hover_text' => '#ffffff', 'admbud_colours_submenu_active_bg' => '#555555', 'admbud_colours_submenu_active_text' => '#ffffff', 'admbud_colours_adminbar_bg' => '#141414', 'admbud_colours_adminbar_text' => '#c8c8c8', 'admbud_colours_adminbar_hover_bg' => '#555555', 'admbud_colours_adminbar_submenu_bg' => '#141414', 'admbud_colours_adminbar_hover_text' => '#ffffff', 'admbud_colours_adminbar_sub_text' => '#c8c8c8', 'admbud_colours_adminbar_sub_hover_bg' => '', 'admbud_colours_adminbar_sub_hover_text' => '#ffffff', 'admbud_colours_shadow_colour' => '', 'admbud_colours_pill_maintenance' => '#dd3333', 'admbud_colours_pill_coming_soon' => '#dd3333', 'admbud_colours_pill_noindex' => '#dd9933', 'admbud_colours_pill_admin_buddy' => '#555555', // Login page 'admbud_login_bg_type' => 'solid', 'admbud_login_bg_color' => '#1a1a1a', 'admbud_login_grad_from' => '#2a2a2a', 'admbud_login_grad_to' => '#1a1a1a', 'admbud_login_grad_direction' => 'to bottom right', // Coming Soon page 'admbud_cs_bg_type' => 'gradient', 'admbud_cs_bg_color' => '#1a1a1a', 'admbud_cs_grad_from' => '#2a2a2a', 'admbud_cs_grad_to' => '#1a1a1a', 'admbud_cs_grad_direction' => 'to bottom right', 'admbud_cs_text_color' => '#e5e5e5', 'admbud_cs_message_color' => '#b0b0b0', // Maintenance page 'admbud_maint_bg_type' => 'gradient', 'admbud_maint_bg_color' => '#1a1a1a', 'admbud_maint_grad_from' => '#2a2a2a', 'admbud_maint_grad_to' => '#1a1a1a', 'admbud_maint_grad_direction' => 'to bottom right', 'admbud_maint_text_color' => '#e5e5e5', 'admbud_maint_message_color' => '#b0b0b0', // Content area 'admbud_colours_content_heading' => '#1d2327', 'admbud_colours_content_text' => '#3c434a', 'admbud_colours_content_link' => '#555555', 'admbud_colours_content_link_hover' => '#333333', 'admbud_colours_table_header_bg' => '#f0f0f0', 'admbud_colours_table_header_text' => '#1d2327', 'admbud_colours_table_header_link' => '#1d2327', 'admbud_colours_table_row_bg' => '#ffffff', 'admbud_colours_table_row_text' => '#3c434a', 'admbud_colours_table_row_alt_bg' => '#f7f7f7', 'admbud_colours_table_row_alt_text' => '#3c434a', 'admbud_colours_table_row_hover' => '#eeeeee', 'admbud_colours_table_border' => '#d5d5d5', 'admbud_colours_table_row_separator' => '#e5e5e5', 'admbud_colours_table_title_link' => '#555555', 'admbud_colours_table_action_link' => '#555555', 'admbud_colours_input_bg' => '#ffffff', 'admbud_colours_input_border' => '#c0c0c0', 'admbud_colours_input_focus' => '#555555', 'admbud_colours_btn_primary_bg' => '#555555', 'admbud_colours_btn_primary_text' => '#ffffff', 'admbud_colours_btn_primary_hover' => '#444444', 'admbud_colours_btn_secondary_bg' => '#f5f5f5', 'admbud_colours_postbox_bg' => '#ffffff', 'admbud_colours_postbox_header_bg' => '#f7f7f7', 'admbud_colours_postbox_border' => '#d5d5d5', 'admbud_colours_notice_bg' => '#fafafa', ], ], 'whitey' => [ 'label' => 'Whitey', 'description' => 'Clean light sidebar with white and soft gray tones. Airy and modern.', 'swatches' => [ '#4b5563', '#374151', '#ffffff', '#f3f4f6', '#1f2937' ], 'values' => [ 'admbud_colours_primary' => '#4b5563', 'admbud_colours_secondary' => '#374151', 'admbud_colours_hover_bg' => '#374151', 'admbud_colours_active_bg' => '#4b5563', // Light sidebar: menu_text is a tinted-neutral dark (~5% // saturation, slate hue) so labels + dashicons read neutral // and the dark-forced plugin SVG icons blend in. Any FUTURE // light preset (menu_bg luminance > 0.55) should likewise // store a low-saturation menu_text - see Colours::lightness() // and the Auto Palette desat() generator for the convention. 'admbud_colours_menu_text' => '#414347', 'admbud_colours_menu_bg' => '#ffffff', 'admbud_colours_active_text' => '#ffffff', 'admbud_colours_sep_color' => '#e5e7eb', 'admbud_colours_body_bg' => '#ffffff', 'admbud_colours_menu_item_sep' => '1', 'admbud_colours_sidebar_gradient' => '1', 'admbud_colours_sidebar_grad_dir' => 'to bottom', 'admbud_colours_sidebar_grad_from' => '#f3f4f6', 'admbud_colours_sidebar_grad_to' => '#ffffff', 'admbud_colours_submenu_text' => '#f3f4f6', 'admbud_colours_hover_text' => '#ffffff', 'admbud_colours_active_parent_text' => '#ffffff', 'admbud_colours_submenu_bg' => '#4b5563', 'admbud_colours_submenu_hover_bg' => '#e5e7eb', 'admbud_colours_submenu_hover_text' => '#111827', 'admbud_colours_submenu_active_bg' => '#4b5563', 'admbud_colours_submenu_active_text' => '#ffffff', 'admbud_colours_adminbar_bg' => '#f3f4f6', // Admin bar + flyout text match the sidebar menu_text for consistency. 'admbud_colours_adminbar_text' => '#414347', 'admbud_colours_adminbar_hover_bg' => '#f3f4f6', 'admbud_colours_adminbar_submenu_bg' => '#f3f4f6', 'admbud_colours_adminbar_hover_text' => '#111827', 'admbud_colours_adminbar_sub_text' => '#414347', 'admbud_colours_adminbar_sub_hover_bg' => '', 'admbud_colours_adminbar_sub_hover_text' => '#111827', 'admbud_colours_shadow_colour' => '#d1d5db', 'admbud_colours_pill_maintenance' => '#dc2626', 'admbud_colours_pill_coming_soon' => '#dc2626', 'admbud_colours_pill_noindex' => '#d97706', 'admbud_colours_pill_admin_buddy' => '#4b5563', // Login page 'admbud_login_bg_type' => 'solid', 'admbud_login_bg_color' => '#f3f4f6', 'admbud_login_grad_from' => '#f3f4f6', 'admbud_login_grad_to' => '#ffffff', 'admbud_login_grad_direction' => 'to bottom right', // Coming Soon page 'admbud_cs_bg_type' => 'solid', 'admbud_cs_bg_color' => '#f3f4f6', 'admbud_cs_grad_from' => '#f3f4f6', 'admbud_cs_grad_to' => '#ffffff', 'admbud_cs_grad_direction' => 'to bottom right', 'admbud_cs_text_color' => '#1f2937', 'admbud_cs_message_color' => '#4b5563', // Maintenance page 'admbud_maint_bg_type' => 'solid', 'admbud_maint_bg_color' => '#f3f4f6', 'admbud_maint_grad_from' => '#f3f4f6', 'admbud_maint_grad_to' => '#ffffff', 'admbud_maint_grad_direction' => 'to bottom right', 'admbud_maint_text_color' => '#1f2937', 'admbud_maint_message_color' => '#4b5563', // Content area 'admbud_colours_content_heading' => '#111827', 'admbud_colours_content_text' => '#374151', 'admbud_colours_content_link' => '#4b5563', 'admbud_colours_content_link_hover' => '#1f2937', 'admbud_colours_table_header_bg' => '#f9fafb', 'admbud_colours_table_header_text' => '#111827', 'admbud_colours_table_row_bg' => '#ffffff', 'admbud_colours_table_row_text' => '#374151', 'admbud_colours_table_row_alt_bg' => '#f9fafb', 'admbud_colours_table_row_alt_text' => '#374151', 'admbud_colours_table_row_hover' => '#f3f4f6', 'admbud_colours_table_border' => '#e5e7eb', 'admbud_colours_table_row_separator' => '#f3f4f6', 'admbud_colours_table_title_link' => '#4b5563', 'admbud_colours_table_action_link' => '#4b5563', 'admbud_colours_input_bg' => '#ffffff', 'admbud_colours_input_border' => '#d1d5db', 'admbud_colours_input_focus' => '#4b5563', 'admbud_colours_btn_primary_bg' => '#4b5563', 'admbud_colours_btn_primary_text' => '#ffffff', 'admbud_colours_btn_primary_hover' => '#374151', 'admbud_colours_btn_secondary_bg' => '#f9fafb', 'admbud_colours_postbox_bg' => '#ffffff', 'admbud_colours_postbox_header_bg' => '#f9fafb', 'admbud_colours_postbox_border' => '#e5e7eb', 'admbud_colours_notice_bg' => '#ffffff', ], ], ]; } public function get_enabled_tabs(): array { $stored = admbud_get_option( 'admbud_modules_enabled_tabs', '__not_set__' ); if ( $stored === '__not_set__' || $stored === false || $stored === '' ) { return []; } return array_filter( explode( ',', (string) $stored ) ); } private function render_tab_modules(): void { $settings = $this; // passed to render file as $settings require ADMBUD_DIR . 'includes/render-tab-modules.php'; } private function render_tab_plugin_data(): void { $settings = $this; require ADMBUD_DIR . 'includes/render-tab-plugin-data.php'; } private function render_tab_quick_settings(): void { $settings = $this; require ADMBUD_DIR . 'includes/render-tab-quick-settings.php'; } }