self::OPTION_MENU_RULES, 'arc_dashboard_rules' => self::OPTION_DASHBOARD_RULES, 'arc_admin_bar_rules' => self::OPTION_ADMIN_BAR_RULES, 'arc_login_redirect_rules' => self::OPTION_LOGIN_REDIRECT_RULES, 'arc_acf_rules' => self::OPTION_ACF_RULES, 'arc_branding_settings' => self::OPTION_BRANDING, ]; foreach ($legacy_map as $legacy_key => $new_key) { $legacy_value = get_option($legacy_key, null); if ($legacy_value === null) { continue; } if (false === get_option($new_key, false)) { add_option($new_key, $legacy_value); } delete_option($legacy_key); } } /** * Activation hook. */ public static function activate() { self::maybe_migrate_legacy_options(); if (!get_option(self::OPTION_MENU_RULES)) { add_option(self::OPTION_MENU_RULES, []); } if (!get_option(self::OPTION_DASHBOARD_RULES)) { add_option(self::OPTION_DASHBOARD_RULES, []); } if (!get_option(self::OPTION_ADMIN_BAR_RULES)) { add_option(self::OPTION_ADMIN_BAR_RULES, []); } if (!get_option(self::OPTION_LOGIN_REDIRECT_RULES)) { add_option(self::OPTION_LOGIN_REDIRECT_RULES, []); } if (!get_option(self::OPTION_ACF_RULES)) { add_option(self::OPTION_ACF_RULES, []); } if (!get_option(self::OPTION_BRANDING)) { add_option(self::OPTION_BRANDING, (new self())->default_branding_settings()); } } /** * Register admin menus. */ public function register_admin_menu() { add_menu_page( __('AdBear Roles', 'adbear-roles-admin-access-manager'), __('AdBear Roles', 'adbear-roles-admin-access-manager'), 'manage_options', self::MENU_SLUG, [$this, 'render_roles_page'], 'dashicons-shield-alt', 81 ); add_submenu_page(self::MENU_SLUG, __('Roles & Capabilities', 'adbear-roles-admin-access-manager'), __('Roles & Capabilities', 'adbear-roles-admin-access-manager'), 'manage_options', self::MENU_SLUG, [$this, 'render_roles_page']); add_submenu_page(self::MENU_SLUG, __('Admin Menu Visibility', 'adbear-roles-admin-access-manager'), __('Admin Menu Visibility', 'adbear-roles-admin-access-manager'), 'manage_options', self::MENU_SLUG . '-menus', [$this, 'render_menus_page']); add_submenu_page(self::MENU_SLUG, __('Dashboard Widgets', 'adbear-roles-admin-access-manager'), __('Dashboard Widgets', 'adbear-roles-admin-access-manager'), 'manage_options', self::MENU_SLUG . '-dashboard', [$this, 'render_dashboard_page']); add_submenu_page(self::MENU_SLUG, __('Admin Bar', 'adbear-roles-admin-access-manager'), __('Admin Bar', 'adbear-roles-admin-access-manager'), 'manage_options', self::MENU_SLUG . '-admin-bar', [$this, 'render_admin_bar_page']); add_submenu_page(self::MENU_SLUG, __('Login Redirects', 'adbear-roles-admin-access-manager'), __('Login Redirects', 'adbear-roles-admin-access-manager'), 'manage_options', self::MENU_SLUG . '-redirects', [$this, 'render_redirects_page']); add_submenu_page(self::MENU_SLUG, __('ACF Group Visibility', 'adbear-roles-admin-access-manager'), __('ACF Group Visibility', 'adbear-roles-admin-access-manager'), 'manage_options', self::MENU_SLUG . '-acf', [$this, 'render_acf_page']); add_submenu_page(self::MENU_SLUG, __('Login & Branding', 'adbear-roles-admin-access-manager'), __('Login & Branding', 'adbear-roles-admin-access-manager'), 'manage_options', self::MENU_SLUG . '-branding', [$this, 'render_branding_page']); } /** * Enqueue admin assets. * * @param string $hook Current admin page hook. */ public function enqueue_admin_assets($hook) { if (strpos((string) $hook, self::MENU_SLUG) === false) { return; } wp_register_style('adbam-admin', false, [], self::VERSION); wp_enqueue_style('adbam-admin'); wp_add_inline_style( 'adbam-admin', '.arc-wrap{max-width:1220px}.arc-card{background:#fff;border:1px solid #dcdcde;border-radius:10px;padding:18px;margin:16px 0}.arc-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px}.arc-field{margin-bottom:14px}.arc-field label{font-weight:600;display:block;margin-bottom:6px}.arc-field input[type=text],.arc-field select,.arc-field input[type=url],.arc-field input[type=number]{width:100%;max-width:440px}.arc-cap-list,.arc-menu-list{columns:2;max-height:390px;overflow:auto;border:1px solid #dcdcde;padding:12px;background:#fafafa}.arc-cap-list label,.arc-menu-list label{display:block;margin:0 0 7px}.arc-table th{font-weight:600}.arc-table th,.arc-table td{padding:14px 16px!important;vertical-align:middle!important}.arc-table .arc-actions{min-width:150px}.arc-badge{display:inline-block;background:#f0f0f1;border-radius:20px;padding:2px 8px;font-size:12px}.arc-actions,.arc-bulk-actions{display:flex;gap:8px;align-items:center;flex-wrap:wrap}.arc-actions{white-space:nowrap}.arc-actions form{margin:0!important;display:inline-flex}.arc-actions .button{min-width:64px;justify-content:center}.arc-bulk-actions{margin:0 0 10px}.arc-warning{background:#fff8e5;border-left:4px solid #dba617;padding:10px 12px;margin:12px 0}.arc-info{background:#f0f6fc;border-left:4px solid #72aee6;padding:10px 12px;margin:12px 0}.arc-submit-row{margin-top:14px}.arc-muted{color:#646970}.arc-code{background:#f6f7f7;border:1px solid #dcdcde;padding:12px;border-radius:8px}.arc-media-preview img{max-width:180px;height:auto;border-radius:8px;border:1px solid #dcdcde}@media(max-width:900px){.arc-grid{grid-template-columns:1fr}.arc-cap-list,.arc-menu-list{columns:1}}' ); if (strpos((string) $hook, 'adbear-admin-access-our-plugins') !== false) { return; } wp_enqueue_media(); wp_register_script('adbam-admin-js', false, ['jquery'], self::VERSION, true); wp_enqueue_script('adbam-admin-js'); wp_add_inline_script( 'adbam-admin-js', "jQuery(function($){ $('.arc-select-all').on('click',function(e){e.preventDefault();$(this).closest('form').find('input[type=checkbox]').prop('checked',true);}); $('.arc-clear-all').on('click',function(e){e.preventDefault();$(this).closest('form').find('input[type=checkbox]').prop('checked',false);}); $('.arc-media-upload').on('click',function(e){e.preventDefault();var input=$(this).data('target');var frame=wp.media({title:'Select Image',button:{text:'Use Image'},multiple:false});frame.on('select',function(){var url=frame.state().get('selection').first().toJSON().url;$('#'+input).val(url);});frame.open();}); });" ); } /** * Handle admin form submissions. */ public function handle_forms() { if (!is_admin() || !current_user_can('manage_options')) { return; } // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Bail before verifying when this plugin form was not submitted. if (!isset($_POST['adbam_action'])) { return; } check_admin_referer(self::NONCE_ACTION, self::NONCE_FIELD); $action = sanitize_key($this->get_post_value('adbam_action', '')); $allowed_actions = [ 'save_role', 'delete_role', 'clone_role', 'save_default_role', 'save_menu_rules', 'save_dashboard_rules', 'save_admin_bar_rules', 'save_login_redirects', 'save_acf_rules', 'save_branding', ]; if (!in_array($action, $allowed_actions, true)) { return; } switch ($action) { case 'save_role': $this->save_role(); break; case 'delete_role': $this->delete_role(); break; case 'clone_role': $this->clone_role(); break; case 'save_default_role': $this->save_default_role(); break; case 'save_menu_rules': $this->save_menu_rules(); break; case 'save_dashboard_rules': $this->save_dashboard_rules(); break; case 'save_admin_bar_rules': $this->save_admin_bar_rules(); break; case 'save_login_redirects': $this->save_login_redirects(); break; case 'save_acf_rules': $this->save_acf_rules(); break; case 'save_branding': $this->save_branding(); break; } } /** * Retrieve a POST value after nonce verification in handle_forms(). * * @param string $key Field key. * @param mixed $default Default value. * @return mixed */ private function get_post_value($key, $default = '') { // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Verified in handle_forms(). if (!isset($_POST[$key])) { return $default; } // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Verified in handle_forms(); sanitized by callers. return wp_unslash($_POST[$key]); } /** * Retrieve a GET query argument for read-only admin UI state. * * @param string $key Query key. * @param string $default Default value. * @return string */ private function get_query_arg($key, $default = '') { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin navigation parameter. if (!isset($_GET[$key])) { return $default; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Read-only admin navigation parameter; sanitized by callers. $value = wp_unslash($_GET[$key]); return is_string($value) ? $value : $default; } /** * Redirect after form save. * * @param string $notice Notice key. * @param string $page Page slug. */ private function redirect($notice, $page = self::MENU_SLUG) { $notice = sanitize_key($notice); $url = add_query_arg('adbam_notice', $notice, menu_page_url($page, false)); wp_safe_redirect($url); exit; } /** * Sanitize internal redirect URL. * * @param string $url Raw URL. * @return string */ private function sanitize_internal_redirect_url($url) { $url = trim((string) $url); if ($url === '') { return ''; } if ($url[0] === '/') { $url = home_url($url); } $url = esc_url_raw($url); if ($url === '') { return ''; } $validated = wp_validate_redirect($url, admin_url()); return $validated ? $validated : ''; } /** * Sanitize CSS gradient value. * * @param string $value Raw gradient. * @return string */ private function sanitize_css_gradient($value) { $value = wp_strip_all_tags((string) $value); $value = preg_replace('/[\x00-\x1F\x7F]/', '', $value); $value = trim($value); if ($value !== '' && preg_match('/^(linear-gradient|radial-gradient)\([^;{}<>\\\\]+\)$/i', $value)) { return $value; } return 'linear-gradient(135deg,#eef4ff 0%,#ffffff 45%,#f5f7fb 100%)'; } /** * Save role and capabilities. */ private function save_role() { global $wp_roles; $role_key = sanitize_key($this->get_post_value('role_key', '')); $role_label = sanitize_text_field($this->get_post_value('role_label', '')); $original_role = sanitize_key($this->get_post_value('original_role', '')); $custom_caps = sanitize_textarea_field($this->get_post_value('custom_caps', '')); $caps_raw = $this->get_post_value('caps', []); $caps = is_array($caps_raw) ? array_map('sanitize_key', $caps_raw) : []; if ($role_key === '' || $role_label === '') { $this->redirect('error'); } if ($role_key === 'administrator') { $this->redirect('protected'); } $cap_map = []; foreach ($caps as $cap) { if ($cap !== '') { $cap_map[$cap] = true; } } foreach (preg_split('/[\s,]+/', $custom_caps) as $cap) { $cap = sanitize_key($cap); if ($cap !== '') { $cap_map[$cap] = true; } } if ($original_role && $original_role !== $role_key && get_role($original_role)) { remove_role($original_role); } $existing = get_role($role_key); if ($existing) { foreach ($existing->capabilities as $cap => $grant) { $existing->remove_cap($cap); } foreach ($cap_map as $cap => $grant) { $existing->add_cap($cap, $grant); } $wp_roles->roles[$role_key]['name'] = $role_label; $wp_roles->role_names[$role_key] = $role_label; update_option('wp_user_roles', $wp_roles->roles); } else { add_role($role_key, $role_label, $cap_map); } $this->redirect('saved'); } /** * Delete a custom role. */ private function delete_role() { $role_key = sanitize_key($this->get_post_value('role_key', '')); $protected = ['administrator', 'editor', 'author', 'contributor', 'subscriber']; if ($role_key && !in_array($role_key, $protected, true)) { remove_role($role_key); $option_keys = [ self::OPTION_MENU_RULES, self::OPTION_DASHBOARD_RULES, self::OPTION_ADMIN_BAR_RULES, self::OPTION_ACF_RULES, ]; foreach ($option_keys as $option_key) { $rules = get_option($option_key, []); if (isset($rules[$role_key])) { unset($rules[$role_key]); update_option($option_key, $rules); } } $redirects = get_option(self::OPTION_LOGIN_REDIRECT_RULES, []); if (isset($redirects[$role_key])) { unset($redirects[$role_key]); update_option(self::OPTION_LOGIN_REDIRECT_RULES, $redirects); } } $this->redirect('deleted'); } /** * Clone an existing role. */ private function clone_role() { global $wp_roles; $source = sanitize_key($this->get_post_value('source_role', '')); $new_key = sanitize_key($this->get_post_value('new_role_key', '')); $new_label = sanitize_text_field($this->get_post_value('new_role_label', '')); if (!$source || !$new_key || !$new_label || get_role($new_key)) { $this->redirect('error'); } $source_role = get_role($source); if (!$source_role) { $this->redirect('error'); } add_role($new_key, $new_label, $source_role->capabilities); $this->redirect('cloned'); } /** * Save default registration role. */ private function save_default_role() { $role_key = sanitize_key($this->get_post_value('default_role', '')); if ($role_key && get_role($role_key)) { update_option('default_role', $role_key); } $this->redirect('default_saved'); } /** * Save admin menu visibility rules. */ private function save_menu_rules() { $role_key = sanitize_key($this->get_post_value('role_key', '')); $allowed_menus_raw = $this->get_post_value('allowed_menus', []); $allowed_menus = is_array($allowed_menus_raw) ? array_map('sanitize_text_field', $allowed_menus_raw) : []; $rules_enabled = !empty($this->get_post_value('rules_enabled', '')); if (!$role_key) { $this->redirect('error', self::MENU_SLUG . '-menus'); } $rules = get_option(self::OPTION_MENU_RULES, []); if ($rules_enabled) { $rules[$role_key] = array_values(array_unique(array_filter($allowed_menus))); } else { unset($rules[$role_key]); } update_option(self::OPTION_MENU_RULES, $rules); $this->redirect('menu_saved', self::MENU_SLUG . '-menus'); } /** * Save dashboard widget rules. */ private function save_dashboard_rules() { $role_key = sanitize_key($this->get_post_value('role_key', '')); $allowed_raw = $this->get_post_value('allowed_widgets', []); $allowed = is_array($allowed_raw) ? array_map('sanitize_text_field', $allowed_raw) : []; $rules_enabled = !empty($this->get_post_value('rules_enabled', '')); if (!$role_key) { $this->redirect('error', self::MENU_SLUG . '-dashboard'); } $rules = get_option(self::OPTION_DASHBOARD_RULES, []); if ($rules_enabled) { $rules[$role_key] = array_values(array_unique(array_filter($allowed))); } else { unset($rules[$role_key]); } update_option(self::OPTION_DASHBOARD_RULES, $rules); $this->redirect('dashboard_saved', self::MENU_SLUG . '-dashboard'); } /** * Save admin bar rules. */ private function save_admin_bar_rules() { $role_key = sanitize_key($this->get_post_value('role_key', '')); $allowed_raw = $this->get_post_value('allowed_nodes', []); $allowed = is_array($allowed_raw) ? array_map('sanitize_key', $allowed_raw) : []; $rules_enabled = !empty($this->get_post_value('rules_enabled', '')); if (!$role_key) { $this->redirect('error', self::MENU_SLUG . '-admin-bar'); } $rules = get_option(self::OPTION_ADMIN_BAR_RULES, []); if ($rules_enabled) { $rules[$role_key] = array_values(array_unique(array_filter($allowed))); } else { unset($rules[$role_key]); } update_option(self::OPTION_ADMIN_BAR_RULES, $rules); $this->redirect('admin_bar_saved', self::MENU_SLUG . '-admin-bar'); } /** * Save login redirect rules. */ private function save_login_redirects() { $redirects = []; $redirects_raw = $this->get_post_value('redirects', []); if (!empty($redirects_raw) && is_array($redirects_raw)) { foreach ($redirects_raw as $role => $url) { $role = sanitize_key($role); $url = $this->sanitize_internal_redirect_url($url); if ($role && $url !== '') { $redirects[$role] = $url; } } } update_option(self::OPTION_LOGIN_REDIRECT_RULES, $redirects); $this->redirect('redirects_saved', self::MENU_SLUG . '-redirects'); } /** * Save ACF group visibility rules. */ private function save_acf_rules() { $role_key = sanitize_key($this->get_post_value('role_key', '')); $allowed_raw = $this->get_post_value('allowed_groups', []); $allowed = is_array($allowed_raw) ? array_map('sanitize_text_field', $allowed_raw) : []; $rules_enabled = !empty($this->get_post_value('rules_enabled', '')); if (!$role_key) { $this->redirect('error', self::MENU_SLUG . '-acf'); } $rules = get_option(self::OPTION_ACF_RULES, []); if ($rules_enabled) { $rules[$role_key] = array_values(array_unique(array_filter($allowed))); } else { unset($rules[$role_key]); } update_option(self::OPTION_ACF_RULES, $rules); $this->redirect('acf_saved', self::MENU_SLUG . '-acf'); } /** * Save login branding settings. */ private function save_branding() { $settings = $this->default_branding_settings(); $settings['enabled'] = !empty($this->get_post_value('enabled', '')) ? 1 : 0; $settings['logo_url'] = esc_url_raw($this->get_post_value('logo_url', '')); $settings['background_type'] = sanitize_key($this->get_post_value('background_type', 'gradient')); if (!in_array($settings['background_type'], ['color', 'gradient', 'image'], true)) { $settings['background_type'] = 'gradient'; } $settings['background_color'] = sanitize_hex_color($this->get_post_value('background_color', '#f3f6fb')); if (!$settings['background_color']) { $settings['background_color'] = '#f3f6fb'; } $settings['background_gradient'] = $this->sanitize_css_gradient($this->get_post_value('background_gradient', $settings['background_gradient'])); $settings['background_image'] = esc_url_raw($this->get_post_value('background_image', '')); $settings['button_color'] = sanitize_hex_color($this->get_post_value('button_color', '#2271b1')); if (!$settings['button_color']) { $settings['button_color'] = '#2271b1'; } $settings['card_radius'] = min(40, absint($this->get_post_value('card_radius', 18))); $logo_link = esc_url_raw($this->get_post_value('logo_link', home_url('/'))); $settings['logo_link'] = $logo_link !== '' ? $logo_link : home_url('/'); $settings['logo_title'] = sanitize_text_field($this->get_post_value('logo_title', get_bloginfo('name'))); $settings['footer_text'] = wp_kses_post($this->get_post_value('footer_text', '')); $settings['login_layout'] = sanitize_key($this->get_post_value('login_layout', 'center')); if (!in_array($settings['login_layout'], ['center', 'split'], true)) { $settings['login_layout'] = 'center'; } $settings['side_image'] = esc_url_raw($this->get_post_value('side_image', '')); $settings['admin_modern_enabled'] = !empty($this->get_post_value('admin_modern_enabled', '')) ? 1 : 0; update_option(self::OPTION_BRANDING, $settings); $this->redirect('branding_saved', self::MENU_SLUG . '-branding'); } /** * Default branding settings. * * @return array */ private function default_branding_settings() { return [ 'enabled' => 0, 'logo_url' => '', 'background_type' => 'gradient', 'background_color' => '#f3f6fb', 'background_gradient' => 'linear-gradient(135deg,#eef4ff 0%,#ffffff 45%,#f5f7fb 100%)', 'background_image' => '', 'button_color' => '#2271b1', 'card_radius' => 18, 'logo_link' => home_url('/'), 'logo_title' => get_bloginfo('name'), 'footer_text' => '', 'login_layout' => 'center', 'side_image' => '', 'admin_modern_enabled' => 0, ]; } /** * Get branding settings. * * @return array */ private function get_branding_settings() { $settings = wp_parse_args((array) get_option(self::OPTION_BRANDING, []), $this->default_branding_settings()); $settings['background_gradient'] = $this->sanitize_css_gradient($settings['background_gradient']); $background_color = sanitize_hex_color($settings['background_color']); $settings['background_color'] = $background_color ? $background_color : '#f3f6fb'; $button_color = sanitize_hex_color($settings['button_color']); $settings['button_color'] = $button_color ? $button_color : '#2271b1'; $settings['logo_url'] = esc_url_raw($settings['logo_url']); $settings['background_image'] = esc_url_raw($settings['background_image']); $settings['side_image'] = esc_url_raw($settings['side_image']); $settings['logo_link'] = esc_url_raw($settings['logo_link']); if (!in_array($settings['background_type'], ['color', 'gradient', 'image'], true)) { $settings['background_type'] = 'gradient'; } if (!in_array($settings['login_layout'], ['center', 'split'], true)) { $settings['login_layout'] = 'center'; } $settings['card_radius'] = min(40, absint($settings['card_radius'])); return $settings; } /** * Get current user's primary role for rule application. * * @return string */ private function get_current_user_role() { $user = wp_get_current_user(); if (!$user || empty($user->roles)) { return ''; } if (in_array('administrator', $user->roles, true)) { return ''; } return (string) $user->roles[0]; } /** * Apply admin menu visibility rules. */ public function apply_menu_rules() { $role_key = $this->get_current_user_role(); if ($role_key === '') { return; } $rules = get_option(self::OPTION_MENU_RULES, []); if (empty($rules[$role_key]) || !is_array($rules[$role_key])) { return; } global $menu, $submenu; $allowed = $rules[$role_key]; $allowed_lookup = array_flip($allowed); if (!empty($menu)) { foreach ($menu as $index => $item) { $slug = isset($item[2]) ? $item[2] : ''; if ($slug && !isset($allowed_lookup[$slug])) { remove_menu_page($slug); } } } if (!empty($submenu)) { foreach ($submenu as $parent => $items) { if (!isset($allowed_lookup[$parent])) { remove_menu_page($parent); } } } } /** * Block direct URL access to hidden admin pages. */ public function enforce_admin_page_access() { if (!is_admin() || wp_doing_ajax() || (defined('DOING_CRON') && DOING_CRON)) { return; } $role_key = $this->get_current_user_role(); if ($role_key === '') { return; } $rules = get_option(self::OPTION_MENU_RULES, []); if (empty($rules[$role_key]) || !is_array($rules[$role_key])) { return; } $current_key = $this->get_current_admin_rule_key(); if ($current_key === '' || $this->is_current_admin_page_allowed($rules[$role_key], $current_key)) { return; } wp_die( esc_html__('You do not have permission to access this admin page.', 'adbear-roles-admin-access-manager'), esc_html__('Access denied', 'adbear-roles-admin-access-manager'), ['response' => 403] ); } /** * Get current admin page rule key. * * @return string */ private function get_current_admin_rule_key() { global $plugin_page, $pagenow; if (!empty($plugin_page)) { return sanitize_text_field($plugin_page); } if ($this->get_query_arg('page') !== '') { return sanitize_text_field($this->get_query_arg('page')); } if ($this->get_query_arg('post_type') !== '') { return sanitize_key($this->get_query_arg('post_type')); } return sanitize_file_name((string) $pagenow); } /** * Check if current admin page is allowed. * * @param array $allowed Allowed keys. * @param string $current_key Current key. * @return bool */ private function is_current_admin_page_allowed(array $allowed, $current_key) { if (in_array($current_key, $allowed, true)) { return true; } foreach ($allowed as $item) { if (strpos($item, '.php') !== false && strpos($current_key, $item) !== false) { return true; } } return false; } /** * Apply dashboard widget visibility rules. */ public function apply_dashboard_rules() { $role_key = $this->get_current_user_role(); if ($role_key === '') { return; } $rules = get_option(self::OPTION_DASHBOARD_RULES, []); if (empty($rules[$role_key]) || !is_array($rules[$role_key])) { return; } global $wp_meta_boxes; $allowed = array_flip($rules[$role_key]); foreach ((array) $wp_meta_boxes['dashboard'] as $context => $priorities) { foreach ((array) $priorities as $priority => $boxes) { foreach ((array) $boxes as $box_id => $box) { if (!isset($allowed[$box_id])) { remove_meta_box($box_id, 'dashboard', $context); } } } } } /** * Apply admin bar visibility rules. * * @param WP_Admin_Bar $wp_admin_bar Admin bar instance. */ public function apply_admin_bar_rules($wp_admin_bar) { $role_key = $this->get_current_user_role(); if ($role_key === '' || !is_object($wp_admin_bar)) { return; } $rules = get_option(self::OPTION_ADMIN_BAR_RULES, []); if (empty($rules[$role_key]) || !is_array($rules[$role_key])) { return; } $allowed = array_flip($rules[$role_key]); $nodes = $wp_admin_bar->get_nodes(); foreach ((array) $nodes as $node) { if (!isset($allowed[$node->id])) { $wp_admin_bar->remove_node($node->id); } } } /** * Apply login redirect by role. * * @param string $redirect_to Redirect URL. * @param string $requested_redirect_to Requested redirect. * @param WP_User|WP_Error $user User object. * @return string */ public function apply_login_redirect($redirect_to, $requested_redirect_to, $user) { if (!$user || is_wp_error($user) || empty($user->roles)) { return $redirect_to; } $redirects = get_option(self::OPTION_LOGIN_REDIRECT_RULES, []); foreach ((array) $user->roles as $role) { if (!empty($redirects[$role])) { $validated = wp_validate_redirect($redirects[$role], admin_url()); if ($validated) { return $validated; } } } return $redirect_to; } /** * Apply ACF field group visibility rules. * * @param array> $groups Field groups. * @return array> */ public function apply_acf_rules($groups) { if (!is_admin() || !function_exists('acf_get_field_groups')) { return $groups; } $role_key = $this->get_current_user_role(); if ($role_key === '') { return $groups; } $rules = get_option(self::OPTION_ACF_RULES, []); if (empty($rules[$role_key]) || !is_array($rules[$role_key])) { return $groups; } $allowed = array_flip($rules[$role_key]); $filtered = []; foreach ((array) $groups as $group) { $key = isset($group['key']) ? $group['key'] : ''; if ($key && isset($allowed[$key])) { $filtered[] = $group; } } return $filtered; } /** * Enqueue login branding styles. */ public function enqueue_login_branding() { $settings = $this->get_branding_settings(); if (empty($settings['enabled'])) { return; } $bg = ''; if ($settings['background_type'] === 'color') { $bg = 'background:' . esc_attr($settings['background_color']) . '!important;'; } elseif ($settings['background_type'] === 'image' && !empty($settings['background_image'])) { $bg = 'background:url(' . esc_url($settings['background_image']) . ') center/cover no-repeat!important;'; } else { $bg = 'background:' . esc_attr($settings['background_gradient']) . '!important;'; } $radius = absint($settings['card_radius']); $button = esc_attr($settings['button_color']); $logo = !empty($settings['logo_url']) ? esc_url($settings['logo_url']) : ''; $layout = $settings['login_layout']; $side_image = !empty($settings['side_image']) ? esc_url($settings['side_image']) : ''; $is_split = ($layout === 'split' && $side_image); $css = 'body.login{margin:0!important;padding:0!important;min-height:100vh!important;box-sizing:border-box;}'; $css .= 'body.login #login{width:100%;max-width:420px;padding:0!important;margin:0!important;float:none!important;}'; $css .= 'body.login #login h1{text-align:center;margin-bottom:18px;}'; $css .= 'body.login #nav,body.login #backtoblog{text-align:center;margin:12px 0 0;padding:0;}'; $css .= 'body.login #nav a,body.login #backtoblog a{display:inline-block;}'; $css .= '#loginform,#registerform,#lostpasswordform{background:#fff;border:0;border-radius:' . $radius . 'px;box-shadow:0 18px 50px rgba(15,23,42,.12);padding:28px;margin:0;}'; $css .= '.wp-core-ui .button-primary{background:' . $button . '!important;border-color:' . $button . '!important;border-radius:10px;box-shadow:none;}'; if ($logo) { $css .= '#login h1 a{background-image:url(' . $logo . ');background-size:contain;background-position:center;width:100%;height:84px;margin:0 auto 18px;}'; } if ($is_split) { $css .= 'body.login{display:grid!important;grid-template-columns:minmax(0,1fr) minmax(0,1fr);align-items:stretch;background:#fff!important;}'; $css .= 'body.login::before{content:"";grid-column:1;grid-row:1;background:url(' . $side_image . ') center/cover no-repeat;min-height:100vh;}'; $css .= 'body.login #login{grid-column:2;grid-row:1;justify-self:center;align-self:center;width:100%;max-width:420px;padding:40px 24px!important;min-height:auto;background:transparent;}'; $css .= 'body.login #language-switcher{grid-column:2;grid-row:1;align-self:end;justify-self:center;position:relative!important;top:auto!important;right:auto!important;left:auto!important;bottom:auto!important;margin:0 0 24px;width:auto;z-index:2;}'; } else { $css .= 'body.login{' . $bg . 'display:flex!important;flex-direction:column;align-items:center;justify-content:center;padding:24px!important;}'; $css .= 'body.login #login{flex:0 0 auto;width:100%;max-width:420px;}'; $css .= 'body.login #language-switcher{position:relative!important;top:auto!important;right:auto!important;left:auto!important;bottom:auto!important;margin:16px 0 0;text-align:center;width:100%;max-width:420px;}'; } wp_register_style('adbam-login', false, [], self::VERSION); wp_enqueue_style('adbam-login'); wp_add_inline_style('adbam-login', $css); } /** * Filter login logo URL. * * @param string $url Logo URL. * @return string */ public function login_logo_url($url) { $settings = $this->get_branding_settings(); if (!empty($settings['enabled']) && !empty($settings['logo_link'])) { return esc_url($settings['logo_link']); } return $url; } /** * Filter login logo title. * * @param string $title Logo title. * @return string */ public function login_logo_title($title) { $settings = $this->get_branding_settings(); if (!empty($settings['enabled']) && !empty($settings['logo_title'])) { return esc_attr($settings['logo_title']); } return $title; } /** * Filter admin footer text. * * @param string $text Footer text. * @return string */ public function admin_footer_text($text) { $settings = $this->get_branding_settings(); if (!empty($settings['enabled']) && !empty($settings['footer_text'])) { return wp_kses_post($settings['footer_text']); } return $text; } /** * Enqueue modern admin CSS when enabled. */ public function enqueue_modern_admin_assets() { $settings = $this->get_branding_settings(); if (empty($settings['admin_modern_enabled'])) { return; } wp_register_style('adbam-modern-admin', false, [], self::VERSION); wp_enqueue_style('adbam-modern-admin'); wp_add_inline_style( 'adbam-modern-admin', '#wpcontent{background:transparent!important;padding-left:100px;}' . '#adminmenu,#adminmenu .wp-submenu,#adminmenuback,#adminmenuwrap{width:260px!important;}' . '#adminmenuback,#adminmenuwrap,#adminmenu{background:#0f172a!important;}' . '#adminmenu a,#adminmenu .wp-submenu a{color:#cbd5e1!important;}' . '#adminmenu .wp-has-current-submenu>a,#adminmenu .current a.menu-top{color:#fff!important;background:#2563eb!important;}' . '#adminmenuwrap{box-shadow:10px 0 35px rgba(15,23,42,.16);}' . '#wpbody-content .wrap h1{font-weight:700;letter-spacing:-.02em;}' . '.arc-card,.postbox,.card{border-radius:14px!important;box-shadow:0 1px 3px rgba(15,23,42,.05)!important;}' ); } /** * Get all capabilities. * * @return array */ private function get_all_capabilities() { global $wp_roles; $caps = []; foreach ($wp_roles->roles as $role) { foreach (array_keys((array) $role['capabilities']) as $cap) { $caps[$cap] = $cap; } } ksort($caps); return array_values($caps); } /** * Get admin menu items for rules UI. * * @return array */ private function get_admin_menus() { global $menu; $items = []; if (!is_array($menu)) { return $items; } foreach ($menu as $item) { if (empty($item[2])) { continue; } $items[] = [ 'slug' => $item[2], 'label' => wp_strip_all_tags((string) $item[0]), ]; } return $items; } /** * Get dashboard widgets for rules UI. * * @return array */ private function get_dashboard_widgets() { global $wp_meta_boxes; $widgets = []; if (!function_exists('wp_dashboard_setup')) { require_once ABSPATH . 'wp-admin/includes/dashboard.php'; } if (empty($wp_meta_boxes['dashboard'])) { wp_dashboard_setup(); } if (!isset($wp_meta_boxes['dashboard']) || !is_array($wp_meta_boxes['dashboard'])) { return $widgets; } foreach ($wp_meta_boxes['dashboard'] as $context => $priorities) { foreach ((array) $priorities as $priority => $boxes) { foreach ((array) $boxes as $box_id => $box) { $widgets[$box_id] = [ 'id' => $box_id, 'label' => isset($box['title']) ? wp_strip_all_tags((string) $box['title']) : $box_id, ]; } } } return array_values($widgets); } /** * Collect raw admin bar nodes from a toolbar instance. * * @param mixed $admin_bar Admin bar instance. * @return array */ private function collect_admin_bar_nodes_from_bar($admin_bar) { if (!is_object($admin_bar) || !method_exists($admin_bar, 'get_nodes')) { return []; } return array_filter((array) $admin_bar->get_nodes()); } /** * Format admin bar nodes for the settings UI. * * @param array $nodes_map Raw admin bar nodes. * @return array */ private function format_admin_bar_nodes_list(array $nodes_map) { $nodes = []; foreach ($nodes_map as $node) { if (!is_object($node) || empty($node->id)) { continue; } $label = trim(wp_strip_all_tags((string) $node->title)); if ($label === '') { $label = (string) $node->id; } $nodes[$node->id] = [ 'id' => (string) $node->id, 'label' => $label, ]; } uasort( $nodes, static function ($a, $b) { return strcasecmp($a['label'], $b['label']); } ); return array_values($nodes); } /** * Bootstrap admin bar nodes for the settings screen. * * @return array */ private function bootstrap_admin_bar_nodes_for_settings() { global $wp_admin_bar; if (!class_exists('WP_Admin_Bar')) { require_once ABSPATH . WPINC . '/class-wp-admin-bar.php'; } if (!function_exists('_wp_admin_bar_init')) { require_once ABSPATH . 'wp-admin/includes/admin-bar.php'; } $existing_nodes = $this->collect_admin_bar_nodes_from_bar($wp_admin_bar); if (!empty($existing_nodes)) { return $this->format_admin_bar_nodes_list($existing_nodes); } if (!did_action('add_admin_bar_menus')) { do_action('add_admin_bar_menus'); } if (!is_object($wp_admin_bar)) { $wp_admin_bar = new WP_Admin_Bar(); $wp_admin_bar->initialize(); } do_action_ref_array('admin_bar_menu', [&$wp_admin_bar]); $nodes_map = $this->collect_admin_bar_nodes_from_bar($wp_admin_bar); if (!empty($nodes_map)) { return $this->format_admin_bar_nodes_list($nodes_map); } $force_show = static function () { return true; }; add_filter('show_admin_bar', $force_show, PHP_INT_MAX); _wp_admin_bar_init(); remove_filter('show_admin_bar', $force_show, PHP_INT_MAX); return $this->format_admin_bar_nodes_list($this->collect_admin_bar_nodes_from_bar($wp_admin_bar)); } /** * Get admin bar nodes for rules UI. * * @return array */ private function get_admin_bar_nodes() { return $this->bootstrap_admin_bar_nodes_for_settings(); } /** * Get ACF field groups for rules UI. * * @return array */ private function get_acf_groups() { if (!function_exists('acf_get_field_groups')) { return []; } $groups = []; foreach (acf_get_field_groups() as $group) { $groups[] = [ 'key' => isset($group['key']) ? $group['key'] : '', 'label' => isset($group['title']) ? $group['title'] : '', ]; } return $groups; } /** * Render admin notice. */ private function render_notice() { $notice = sanitize_key($this->get_query_arg('adbam_notice', '')); if ($notice === '') { return; } $messages = [ 'saved' => __('Role saved successfully.', 'adbear-roles-admin-access-manager'), 'deleted' => __('Role deleted.', 'adbear-roles-admin-access-manager'), 'cloned' => __('Role cloned successfully.', 'adbear-roles-admin-access-manager'), 'default_saved' => __('Default role updated.', 'adbear-roles-admin-access-manager'), 'menu_saved' => __('Admin menu rules saved.', 'adbear-roles-admin-access-manager'), 'dashboard_saved' => __('Dashboard widget rules saved.', 'adbear-roles-admin-access-manager'), 'admin_bar_saved' => __('Admin bar rules saved.', 'adbear-roles-admin-access-manager'), 'redirects_saved' => __('Login redirects saved.', 'adbear-roles-admin-access-manager'), 'acf_saved' => __('ACF group rules saved.', 'adbear-roles-admin-access-manager'), 'branding_saved' => __('Branding settings saved.', 'adbear-roles-admin-access-manager'), 'protected' => __('The administrator role cannot be modified.', 'adbear-roles-admin-access-manager'), 'error' => __('There was a problem saving your changes.', 'adbear-roles-admin-access-manager'), ]; if (!isset($messages[$notice])) { return; } $class = ($notice === 'error' || $notice === 'protected') ? 'notice-error' : 'notice-success'; echo '

' . esc_html($messages[$notice]) . '

'; } /** * Output nonce field. */ private function render_nonce_field() { wp_nonce_field(self::NONCE_ACTION, self::NONCE_FIELD); echo ''; } /** * Render role selector field. * * @param string $selected Selected role. */ private function render_role_selector($selected) { global $wp_roles; $page = sanitize_key($this->get_query_arg('page', self::MENU_SLUG)); echo '
'; echo '
'; } /** * Render roles page. */ public function render_roles_page() { if (!current_user_can('manage_options')) { wp_die(esc_html__('You do not have permission to access this page.', 'adbear-roles-admin-access-manager')); } global $wp_roles; $edit_role_key = sanitize_key($this->get_query_arg('edit_role', '')); $editing = $edit_role_key && get_role($edit_role_key); $all_caps = $this->get_all_capabilities(); $default_role = get_option('default_role', 'subscriber'); ?>

render_notice(); ?>

required />
roles[$edit_role_key]['capabilities']) : []; foreach ($all_caps as $cap) : ?>

roles as $role_key => $role) : ?>
get_query_arg('role_key', 'subscriber')); $rules = get_option(self::OPTION_MENU_RULES, []); $rules_enabled = isset($rules[$selected_role]) && is_array($rules[$selected_role]); $allowed = $rules_enabled ? $rules[$selected_role] : []; $menus = $this->get_admin_menus(); ?>

render_notice(); ?>
render_role_selector($selected_role); ?>

get_query_arg('role_key', 'subscriber')); $rules = get_option(self::OPTION_DASHBOARD_RULES, []); $enabled = isset($rules[$selected_role]) && is_array($rules[$selected_role]); $allowed = $enabled ? $rules[$selected_role] : []; $widgets = $this->get_dashboard_widgets(); ?>

render_notice(); ?>
render_role_selector($selected_role); ?>

get_query_arg('role_key', 'subscriber')); $rules = get_option(self::OPTION_ADMIN_BAR_RULES, []); $enabled = isset($rules[$selected_role]) && is_array($rules[$selected_role]); $allowed = $enabled ? $rules[$selected_role] : []; $nodes = $this->get_admin_bar_nodes(); ?>

render_notice(); ?>
render_role_selector($selected_role); ?>

render_notice(); ?>
roles as $role_key => $role) : ?>

get_query_arg('role_key', 'subscriber')); $rules = get_option(self::OPTION_ACF_RULES, []); $enabled = isset($rules[$selected_role]) && is_array($rules[$selected_role]); $allowed = $enabled ? $rules[$selected_role] : []; $groups = $this->get_acf_groups(); ?>

render_notice(); ?>
render_role_selector($selected_role); ?>

get_branding_settings(); ?>

render_notice(); ?>