__('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);
});