'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_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' => '#1e1b2e',
'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,
// -- 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' => '{}',
];
}
// -- 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'],
'href' => $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,
] );
// 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 ],
];
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 );
// When the Colours module is OFF, AB chrome (buttons, toggles, focus
// rings) should match the user's chosen WordPress admin colour scheme
// — Fresh, Coffee, Ectoplasm, etc. — not AB's brand purple or a
// hardcoded default. `--wp-admin-theme-color` isn't reliably set on
// classic admin pages (it's mostly a block-editor variable), so we
// resolve the scheme accent in PHP from `$_wp_admin_css_colors` and
// emit it as `--ab-primary` scoped to `.ab-wrap`. When the Colours
// module is ON, class-colours.php emits its own `--ab-primary` later
// in the cascade (with !important on --wp-admin-theme-color), which
// takes precedence — so this block is a no-op in that case.
$admbud_enabled = admbud_enabled_modules();
if ( ! is_array( $admbud_enabled ) || ! in_array( 'colours', $admbud_enabled, true ) ) {
global $_wp_admin_css_colors;
$admbud_scheme = get_user_option( 'admin_color' ) ?: 'fresh';
$admbud_accent = '#2271b1'; // Fresh scheme primary; safe fallback.
if ( isset( $_wp_admin_css_colors[ $admbud_scheme ]->colors[2] ) ) {
$admbud_accent = $_wp_admin_css_colors[ $admbud_scheme ]->colors[2];
}
$admbud_a = esc_attr( $admbud_accent );
// Emit BOTH the source vars (--ab-primary, --ab-secondary,
// --wp-admin-theme-color) AND the *derived* accent vars
// (--ab-accent, --ab-accent-hover, etc.). The derived vars are
// computed at :root in tokens.css using var(--ab-primary, …) —
// and CSS custom-property substitution resolves at the element
// where the declaration lives, so a .ab-wrap-scoped override of
// --ab-primary alone does NOT change the :root-computed value of
// --ab-accent. Redeclaring the derived vars at .ab-wrap level is
// what actually re-substitutes them with our scheme value.
$admbud_scheme_css = '.ab-wrap{'
. '--ab-primary:' . $admbud_a . ';'
. '--ab-secondary:' . $admbud_a . ';'
. '--wp-admin-theme-color:' . $admbud_a . ';'
. '--ab-accent:' . $admbud_a . ';'
. '--ab-accent-hover:color-mix(in srgb,' . $admbud_a . ' 85%,#000);'
. '--ab-accent-subtle:color-mix(in srgb,' . $admbud_a . ' 8%,transparent);'
. '--ab-border-focus:' . $admbud_a . ';'
. '}';
wp_add_inline_style( 'admbud-tokens', $admbud_scheme_css );
}
// 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( 'wp-color-picker' );
wp_enqueue_script( 'wp-color-picker' );
wp_enqueue_script( 'admbud-dropdown', $js_url . 'ab-dropdown.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' );
}
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 ( file_exists( ADMBUD_DIR . 'licensing/src/Client.php' ) ) {
$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
?>
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 );
?>
0 ) : ?>
__( '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 = '
';
?>
get_manageable_tabs();
$standalone_slugs = [ 'modules', 'plugin-data', 'license' ];
$module_tabs = array_diff_key( $tabs, array_flip( $standalone_slugs ) );
$has_modules = ! empty( $module_tabs );
?>
__( 'Interface', 'admin-buddy' ),
'utilities' => __( 'Utilities', 'admin-buddy' ),
'integrations' => __( 'Integrations', 'admin-buddy' ),
'manage' => __( 'Manage', 'admin-buddy' ),
];
// -- Collapsible groups (Interface, Utilities, Integrations, Manage) --
// All groups follow the same pattern: filter by enabled modules.
foreach ( $nav_groups as $group_key => $group_label ) :
$group_tabs = array_filter( $module_tabs, function( $slug ) use ( $group_key, $manageable ) {
return isset( $manageable[ $slug ] ) && ( $manageable[ $slug ]['group'] ?? '' ) === $group_key;
}, ARRAY_FILTER_USE_KEY );
if ( empty( $group_tabs ) ) { continue; }
$is_open = ( $active_group === $group_key );
?>
data-group="">
$tab ) :
$is_active = ( $active_tab === $slug );
$url = admin_url( 'admin.php?page=admbud&tab=' . $slug );
?>
>
>
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' => ' ' ],
];
}
/**
* 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 ); }
// 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 );
}
}
/**
* 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 ); }
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' => '#7c3aed',
'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' => '#0d9488',
'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' => '#e11d48',
'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' => '#2563eb',
'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' => '#555555',
'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',
'admbud_colours_menu_text' => '#374151',
'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',
'admbud_colours_adminbar_text' => '#4b5563',
'admbud_colours_adminbar_hover_bg' => '#f3f4f6',
'admbud_colours_adminbar_submenu_bg' => '#f3f4f6',
'admbud_colours_adminbar_hover_text' => '#111827',
'admbud_colours_adminbar_sub_text' => '#4b5563',
'admbud_colours_adminbar_sub_hover_bg' => '#f3f4f6',
'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';
}
}