__('Follow the system', 'adminlane'), 'light' => __('Light', 'adminlane'), 'soft' => __('Soft', 'adminlane'), 'dim' => __('Dim', 'adminlane'), 'dark' => __('Dark', 'adminlane'), ]; } function adminlane_is_mode(string $m): bool { return in_array($m, ['auto', 'light', 'soft', 'dim', 'dark'], true); } /** auto | light | soft | dim | dark, per user. */ function adminlane_mode(): string { $m = (string) get_user_meta(get_current_user_id(), 'adminlane_mode', true); return adminlane_is_mode($m) ? $m : 'auto'; } /** The accent colours, in the order they are offered. Blue is the default. */ function adminlane_accent_labels(): array { return [ 'blue' => __('Blue', 'adminlane'), 'violet' => __('Violet', 'adminlane'), 'teal' => __('Teal', 'adminlane'), 'green' => __('Green', 'adminlane'), 'orange' => __('Orange', 'adminlane'), 'pink' => __('Pink', 'adminlane'), ]; } function adminlane_is_accent(string $a): bool { return array_key_exists($a, adminlane_accent_labels()); } /** The accent colour, per user. */ function adminlane_accent(): string { $a = (string) get_user_meta(get_current_user_id(), 'adminlane_accent', true); return adminlane_is_accent($a) ? $a : 'blue'; } /** * Which kind of copy of the site this is, when it isn't the live one: local, development or * staging (from WP_ENVIRONMENT_TYPE). Empty on production. Shown in the toolbar so a test copy * is never mistaken for the live site. */ function adminlane_environment(): string { $env = function_exists('wp_get_environment_type') ? wp_get_environment_type() : 'production'; $env = in_array($env, ['local', 'development', 'staging'], true) ? $env : ''; /** * Filters the environment badge in the toolbar. Return '' to hide it. * * @param string $env local, development, staging, or '' on production. */ $env = (string) apply_filters('adminlane_environment_badge', $env); return in_array($env, ['local', 'development', 'staging'], true) ? $env : ''; } /** The user has not paused the skin. */ function adminlane_user_on(): bool { return get_user_meta(get_current_user_id(), 'adminlane_paused', true) !== '1' && (bool) apply_filters('adminlane_enabled', true); } /** The user wants other plugins' screens restyled too. */ function adminlane_restyle_others(): bool { return get_user_meta(get_current_user_id(), 'adminlane_others', true) === '1'; } /** WordPress' own screens, by WP_Screen::$base (network admin screens use the same bases). */ function adminlane_core_bases(): array { return [ 'dashboard', 'edit-tags', 'term', 'upload', 'media', 'users', 'user-edit', 'profile', 'user', 'edit-comments', 'comment', 'plugins', 'plugin-install', 'plugin-editor', 'themes', 'theme-install', 'theme-editor', 'nav-menus', 'tools', 'import', 'export', 'export-personal-data', 'erase-personal-data', 'site-health', 'options-general', 'options-writing', 'options-reading', 'options-discussion', 'options-media', 'options-permalink', 'options-privacy', 'privacy-policy-guide', 'update-core', 'update', 'my-sites', 'sites', 'site-new', 'site-info', 'site-users', 'site-themes', 'site-settings', 'settings', 'upgrade', 'ms-delete-site', 'widgets', 'revision', 'options', 'link-manager', 'link', 'link-add', ]; } /** * Content types: Posts, Pages and every other type visitors can see on the site * (a theme's "Museums", WooCommerce's products …). Types that a plugin keeps to * itself — ACF field groups, gallery or form builders and the like — are not * public, so their screens are treated as that plugin's own. * * @return string[] */ function adminlane_content_types(): array { static $types = null; if ($types !== null) return $types; $list = ['post', 'page']; foreach (get_post_types(['show_ui' => true, '_builtin' => false]) as $pt) { if (is_post_type_viewable($pt)) $list[] = $pt; } /** * Filters which post types get the overview, thumbnails and the edit-screen skin. * * @param string[] $list Post type names. */ $types = array_values(array_unique(array_map('strval', (array) apply_filters('adminlane_post_types', $list)))); return $types; } function adminlane_is_content_type(string $post_type): bool { return in_array($post_type, adminlane_content_types(), true); } /** Screens that belong to another plugin: framed only, or restyled if the user opted in. */ function adminlane_others_kind(): string { return adminlane_restyle_others() ? 'page' : 'chrome'; } /** What the skin does on the current screen: edit | list | page | chrome | off. */ function adminlane_screen_kind(): string { static $kind = null; if ($kind !== null) return $kind; if (!is_admin() || !function_exists('get_current_screen') || !adminlane_user_on()) return 'off'; $s = get_current_screen(); if (!$s) return 'off'; if (method_exists($s, 'is_block_editor') && $s->is_block_editor()) { $k = 'off'; } elseif (in_array($s->base, ['site-editor', 'customize', 'about', 'credits', 'freedoms', 'privacy', 'contribute'], true)) { $k = 'off'; // full-screen apps and WordPress' own designed pages } elseif ($s->base === 'widgets' && function_exists('wp_use_widgets_block_editor') && wp_use_widgets_block_editor()) { $k = 'off'; } elseif ($s->base === 'post') { // Edit Media is WordPress' own screen too (its post type, attachment, is never "viewable" as content). if ($s->post_type !== 'attachment' && !adminlane_is_content_type((string) $s->post_type)) $k = adminlane_others_kind(); else $k = apply_filters('adminlane_active', true, $s) ? 'edit' : 'page'; } elseif ($s->base === 'edit' && $s->post_type !== '') { if (!adminlane_is_content_type((string) $s->post_type)) $k = adminlane_others_kind(); else $k = apply_filters('adminlane_list_active', true, $s) ? 'list' : 'page'; } elseif (in_array($s->base, adminlane_core_bases(), true)) { $k = 'page'; } else { $k = adminlane_others_kind(); } /** * Filters what the skin does on a screen. * * @param string $k One of edit, list, page, chrome, off. * @param WP_Screen $s The screen. */ $k = (string) apply_filters('adminlane_screen_kind', $k, $s); $kind = in_array($k, ['edit', 'list', 'page', 'chrome', 'off'], true) ? $k : 'off'; return $kind; } /** The classic edit screen is skinned. (Kept for code that called it in 1.x.) */ function adminlane_active(): bool { return adminlane_screen_kind() === 'edit'; } add_filter('admin_body_class', function ($classes) { $k = adminlane_screen_kind(); if ($k === 'off') return $classes; $add = ['adl', 'adl-' . adminlane_mode(), 'adl-accent-' . adminlane_accent(), 'adl-chrome']; if (adminlane_environment() !== '') $add[] = 'adl-env-' . adminlane_environment(); if ($k !== 'chrome') $add[] = 'adl-page'; if ($k === 'edit') $add[] = 'adl-edit'; if ($k === 'list') array_push($add, 'adl-list', 'adl-has-hero'); // the overview replaces the heading from the first paint $s = get_current_screen(); $own = in_array($s->base, adminlane_core_bases(), true) || (in_array($s->base, ['edit', 'post'], true) && adminlane_is_content_type((string) $s->post_type)) || ($s->base === 'post' && $s->post_type === 'attachment'); if ($k === 'page' && !$own) $add[] = 'adl-others'; return $classes . ' ' . implode(' ', $add) . ' '; }); add_action('admin_enqueue_scripts', function () { if (adminlane_screen_kind() === 'off') return; wp_enqueue_style('adl-tokens', ADMINLANE_URL . 'assets/tokens.css', [], adminlane_ver('tokens.css')); wp_enqueue_style('adl-admin', ADMINLANE_URL . 'assets/admin.css', ['adl-tokens'], adminlane_ver('admin.css')); wp_enqueue_script('adl-admin', ADMINLANE_URL . 'assets/admin.js', [], adminlane_ver('admin.js'), true); wp_localize_script('adl-admin', 'ADMINLANE', [ 'ajaxUrl' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('adminlane_mode'), 'mode' => adminlane_mode(), 'accent' => adminlane_accent(), 'tip' => adminlane_tip_due() ? [ 'title' => __('Your admin, your look', 'adminlane'), 'text' => __('Pick Light, Soft, Dim, Dark or Follow the system here, and an accent colour. Each user chooses their own, and you can pause Adminlane from the same menu.', 'adminlane'), 'ok' => __('Got it', 'adminlane'), ] : null, ]); }, 20); /* --------------------------------------------------------------------- * First-run hint: a small card under the toolbar icon, shown to each user * until they close it or open the menu (at most three page loads). * ------------------------------------------------------------------- */ function adminlane_tip_due(): bool { $seen = (string) get_user_meta(get_current_user_id(), 'adminlane_tip', true); return $seen !== 'done' && (int) $seen < 3; } function adminlane_tip_done(int $uid): void { update_user_meta($uid, 'adminlane_tip', 'done'); } add_action('wp_ajax_adminlane_tip', function () { check_ajax_referer('adminlane_mode', 'nonce'); $uid = get_current_user_id(); if (!empty($_POST['done'])) { adminlane_tip_done($uid); } elseif (adminlane_tip_due()) { update_user_meta($uid, 'adminlane_tip', (string) ((int) get_user_meta($uid, 'adminlane_tip', true) + 1)); } wp_send_json_success(); }); /* --------------------------------------------------------------------- * Saving the mode (from the toolbar, without reloading the page) * ------------------------------------------------------------------- */ add_action('wp_ajax_adminlane_mode', function () { check_ajax_referer('adminlane_mode', 'nonce'); $mode = isset($_POST['mode']) ? sanitize_key(wp_unslash($_POST['mode'])) : ''; if (!adminlane_is_mode($mode)) wp_send_json_error(); update_user_meta(get_current_user_id(), 'adminlane_mode', $mode); adminlane_tip_done(get_current_user_id()); // they found the menu wp_send_json_success(['mode' => $mode]); }); add_action('wp_ajax_adminlane_accent', function () { check_ajax_referer('adminlane_mode', 'nonce'); $accent = isset($_POST['accent']) ? sanitize_key(wp_unslash($_POST['accent'])) : ''; if (!adminlane_is_accent($accent)) wp_send_json_error(); update_user_meta(get_current_user_id(), 'adminlane_accent', $accent); adminlane_tip_done(get_current_user_id()); wp_send_json_success(['accent' => $accent]); }); /* The same menu without JavaScript, and Pause / Turn on: plain links through admin-post.php. */ function adminlane_toolbar_url(string $set): string { return wp_nonce_url(add_query_arg(['action' => 'adminlane_set', 'set' => $set], admin_url('admin-post.php')), 'adminlane_set'); } add_action('admin_post_adminlane_set', function () { check_admin_referer('adminlane_set'); $set = isset($_GET['set']) ? sanitize_key(wp_unslash($_GET['set'])) : ''; $uid = get_current_user_id(); adminlane_tip_done($uid); if (adminlane_is_mode($set)) update_user_meta($uid, 'adminlane_mode', $set); if (str_starts_with($set, 'accent-') && adminlane_is_accent(substr($set, 7))) update_user_meta($uid, 'adminlane_accent', substr($set, 7)); if ($set === 'pause') update_user_meta($uid, 'adminlane_paused', '1'); if ($set === 'resume') delete_user_meta($uid, 'adminlane_paused'); wp_safe_redirect(wp_get_referer() ?: admin_url()); exit; }); /* --------------------------------------------------------------------- * Toolbar: Appearance menu (in the admin only — the site itself is left alone) * ------------------------------------------------------------------- */ add_action('admin_bar_menu', function (WP_Admin_Bar $bar) { if (!is_admin() || !is_user_logged_in()) return; if (!adminlane_user_on()) { if (!(bool) apply_filters('adminlane_enabled', true)) return; $bar->add_node([ 'id' => 'adl', 'parent' => 'top-secondary', 'title' => '' . esc_html__('Adminlane paused', 'adminlane') . '', 'href' => adminlane_toolbar_url('resume'), 'meta' => ['class' => 'adl-ab adl-ab--paused', 'title' => __('Turn Adminlane back on', 'adminlane')], ]); $bar->add_node(['id' => 'adl-resume', 'parent' => 'adl', 'title' => esc_html__('Turn Adminlane back on', 'adminlane'), 'href' => adminlane_toolbar_url('resume')]); return; } if (adminlane_screen_kind() === 'off') return; $mode = adminlane_mode(); $bar->add_node([ 'id' => 'adl', 'parent' => 'top-secondary', 'title' => '' . esc_html__('Appearance', 'adminlane') . '', 'href' => false, 'meta' => ['class' => 'adl-ab', 'title' => __('Appearance', 'adminlane')], ]); foreach (adminlane_mode_labels() as $id => $label) { $bar->add_node([ 'id' => 'adl-' . $id, 'parent' => 'adl', 'title' => '' . esc_html($label), 'href' => adminlane_toolbar_url($id), 'meta' => ['class' => 'adl-ab-item' . ($id === $mode ? ' is-current' : '')], ]); } $accent = adminlane_accent(); $bar->add_group(['id' => 'adl-accents', 'parent' => 'adl', 'meta' => ['class' => 'adl-ab-accents']]); foreach (adminlane_accent_labels() as $id => $label) { $bar->add_node([ 'id' => 'adl-accent-' . $id, 'parent' => 'adl-accents', 'title' => '' /* translators: %s: a colour name, such as Violet. */ . esc_html(sprintf(__('Accent colour: %s', 'adminlane'), $label)) . '', 'href' => adminlane_toolbar_url('accent-' . $id), 'meta' => ['class' => 'adl-ab-accent' . ($id === $accent ? ' is-current' : ''), 'title' => $label], ]); } $bar->add_group(['id' => 'adl-more', 'parent' => 'adl', 'meta' => ['class' => 'ab-sub-secondary']]); $bar->add_node([ 'id' => 'adl-pause', 'parent' => 'adl-more', 'title' => esc_html__('Pause Adminlane', 'adminlane'), 'href' => adminlane_toolbar_url('pause'), ]); $bar->add_node([ 'id' => 'adl-settings', 'parent' => 'adl-more', 'title' => esc_html__('Adminlane settings', 'adminlane'), 'href' => admin_url('profile.php#adl-settings'), ]); }, 95); /* Toolbar: a badge after the site name on local, development and staging copies of the site. */ add_action('admin_bar_menu', function (WP_Admin_Bar $bar) { if (!is_admin() || !adminlane_user_on() || adminlane_screen_kind() === 'off') return; $env = adminlane_environment(); if ($env === '') return; $labels = [ 'local' => __('Local', 'adminlane'), 'development' => __('Development', 'adminlane'), 'staging' => __('Staging', 'adminlane'), ]; $bar->add_node([ 'id' => 'adl-env', 'title' => '' . esc_html($labels[$env]) . '', 'href' => false, 'meta' => [ 'class' => 'adl-env adl-env--' . $env, /* translators: %s: the value of WP_ENVIRONMENT_TYPE (local, development or staging), shown as is. */ 'title' => sprintf(__('Not the live site: WP_ENVIRONMENT_TYPE is %s.', 'adminlane'), $env), ], ]); }, 31); /* The toolbar icon and menu also need the stylesheet while the skin is paused. */ add_action('admin_enqueue_scripts', function () { if (adminlane_user_on() || !(bool) apply_filters('adminlane_enabled', true)) return; wp_enqueue_style('adl-paused', ADMINLANE_URL . 'assets/paused.css', [], adminlane_ver('paused.css')); }); /* --------------------------------------------------------------------- * Settings from "MuseumLite – Editor Skin", the plugin's earlier name: * each user's appearance, pause and other-plugins choice are copied once. * ------------------------------------------------------------------- */ add_action('admin_init', function () { if (get_option('adminlane_migrated')) return; foreach (['mode', 'paused', 'others'] as $key) { // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- runs once per site foreach (get_users(['meta_key' => 'mles_' . $key, 'fields' => 'ID']) as $uid) { if (get_user_meta((int) $uid, 'adminlane_' . $key, true) === '') { update_user_meta((int) $uid, 'adminlane_' . $key, get_user_meta((int) $uid, 'mles_' . $key, true)); } adminlane_tip_done((int) $uid); // they already know the toolbar menu } } update_option('adminlane_migrated', 1, false); });