[allowed menu slugs]. const OPT_ROLES = 'ccamc_roles_config'; // role => [restrict_menu, hide_plugin_notices, hide_wp_info, block_admin, hide_dashboard, landing, hide_admin_bar, grant_caps, grant_sensitive]. const OPT_GENERAL = 'ccamc_general'; // block_direct_access, redirect, redirect_url. const META_BLOCK = 'ccamc_block_admin'; // Per-user admin block flag. const OPT_CAPS = 'ccamc_menu_caps'; // slug => required capabilities (computed from the administrator menu). const OPT_GRANTED = 'ccamc_granted_caps'; // role => capabilities added by the plugin (so they can be removed). /** @var CCAMC_Plugin|null */ private static $instance = null; /** @var string[] Plugin page hook suffixes. */ private $page_hooks = array(); /** @var array|null Per-role configuration cache. */ private $roles_config = null; /** @var array Current user's menu slugs before filtering (normalized slug => parsed slug). */ private $menu_snapshot = array(); /** @var string[]|null Allowed slugs for the current user (null = menu not restricted). */ private $allowed = null; /** @var array|null Interface options cache for the current user. */ private $iface = null; public static function get_instance() { if ( null === self::$instance ) { self::$instance = new self(); } return self::$instance; } private function __construct() { add_action( 'init', array( $this, 'maybe_migrate' ), 5 ); // Admin pages. add_action( 'admin_menu', array( $this, 'register_pages' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) ); add_filter( 'plugin_action_links_' . plugin_basename( CCAMC_FILE ), array( $this, 'action_links' ) ); add_filter( 'plugin_row_meta', array( $this, 'row_meta' ), 10, 2 ); add_filter( 'admin_footer_text', array( $this, 'plugin_footer_text' ), 20 ); // Form handlers. foreach ( array( 'save_menu', 'save_interface', 'save_access', 'create_role', 'delete_role', 'block_user', 'unblock_user' ) as $action ) { add_action( 'admin_post_ccamc_' . $action, array( $this, 'handle_' . $action ) ); } // Menu restriction, direct access and admin access. add_action( 'admin_menu', array( $this, 'filter_admin_menu_for_roles' ), 99999 ); add_action( 'admin_init', array( $this, 'enforce_admin_block' ), 0 ); add_action( 'admin_menu', array( $this, 'hide_dashboard_menu' ), 99998 ); add_action( 'admin_init', array( $this, 'enforce_hidden_dashboard' ), 1 ); add_filter( 'login_redirect', array( $this, 'filter_login_redirect' ), 999, 3 ); add_action( 'admin_init', array( $this, 'enforce_direct_access' ), 2 ); add_filter( 'show_admin_bar', array( $this, 'filter_show_admin_bar' ), 999 ); // Interface cleanup (notifications, WordPress info). add_action( 'admin_init', array( $this, 'maybe_disable_pointers' ) ); add_action( 'in_admin_header', array( $this, 'strip_admin_notices' ), 9999 ); add_action( 'admin_enqueue_scripts', array( $this, 'interface_styles' ) ); add_action( 'admin_head', array( $this, 'interface_help_tabs' ) ); add_action( 'wp_dashboard_setup', array( $this, 'remove_dashboard_widgets' ), 9999 ); add_action( 'admin_bar_menu', array( $this, 'clean_admin_bar' ), 9999 ); add_filter( 'admin_footer_text', array( $this, 'filter_footer_text' ), 9999 ); add_filter( 'update_footer', array( $this, 'filter_footer_text' ), 9999 ); // User profile: per-user block. add_action( 'edit_user_profile', array( $this, 'render_profile_field' ) ); add_action( 'edit_user_profile_update', array( $this, 'save_profile_field' ) ); } /** * Imports the settings of the former private version ("Visibilité Menu", rbamv_ prefix). */ public function maybe_migrate() { if ( get_option( 'ccamc_version' ) ) { return; } foreach ( array( 'settings', 'roles_config', 'general', 'menu_caps', 'granted_caps' ) as $key ) { $old = get_option( 'rbamv_' . $key, null ); if ( null !== $old && null === get_option( 'ccamc_' . $key, null ) ) { update_option( 'ccamc_' . $key, $old, in_array( $key, array( 'menu_caps', 'granted_caps' ), true ) ? false : null ); } } $blocked = get_users( array( 'meta_key' => 'rbamv_block_admin', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key 'fields' => 'ID', 'number' => 1000, ) ); foreach ( $blocked as $user_id ) { update_user_meta( (int) $user_id, self::META_BLOCK, 1 ); } update_option( 'ccamc_version', CCAMC_VERSION ); $this->roles_config = null; } /* ===================================================================== * Configuration * ===================================================================== */ private function role_defaults() { return array( 'restrict_menu' => false, 'hide_plugin_notices' => false, 'hide_wp_info' => false, 'block_admin' => false, 'hide_dashboard' => false, 'landing' => '', 'hide_admin_bar' => false, 'grant_caps' => false, 'grant_sensitive' => false, ); } /** * Configuration of every role (with migration from the very first format). */ private function get_roles_config() { if ( null !== $this->roles_config ) { return $this->roles_config; } $config = get_option( self::OPT_ROLES, null ); if ( ! is_array( $config ) ) { // First format: a role was restricted as soon as one item was checked. $config = array(); $legacy = get_option( self::OPT_MENU, array() ); if ( is_array( $legacy ) ) { foreach ( $legacy as $role => $slugs ) { if ( ! empty( $slugs ) ) { $config[ $role ] = array( 'restrict_menu' => true ); } } } } $this->roles_config = $config; return $config; } private function role_cfg( $role ) { $config = $this->get_roles_config(); $cfg = isset( $config[ $role ] ) ? (array) $config[ $role ] : array(); return wp_parse_args( $cfg, $this->role_defaults() ); } private function save_roles_config( $config ) { update_option( self::OPT_ROLES, $config ); $this->roles_config = $config; } private function get_general() { $general = get_option( self::OPT_GENERAL, array() ); return wp_parse_args( is_array( $general ) ? $general : array(), array( 'block_direct_access' => 1, 'redirect' => 'home', 'redirect_url' => '', ) ); } /* ===================================================================== * Current user * ===================================================================== */ private function user_roles( $user = null ) { $user = $user ? $user : wp_get_current_user(); if ( ! $user || ! $user->exists() ) { return array(); } return array_values( (array) $user->roles ); } private function is_admin_user( $user = null ) { $user = $user ? $user : wp_get_current_user(); if ( ! $user || ! $user->exists() ) { return false; } return is_super_admin( $user->ID ) || in_array( 'administrator', (array) $user->roles, true ); } /** * True when ALL the user's roles have the option enabled * (as with WordPress capabilities, the most permissive role wins). */ private function role_flag_all( $flag, $user = null ) { $roles = $this->user_roles( $user ); if ( empty( $roles ) ) { return false; } foreach ( $roles as $role ) { $cfg = $this->role_cfg( $role ); if ( empty( $cfg[ $flag ] ) ) { return false; } } return true; } /* ===================================================================== * Roles * ===================================================================== */ private function get_roles( $with_admin = false ) { if ( ! function_exists( 'get_editable_roles' ) ) { require_once ABSPATH . 'wp-admin/includes/user.php'; } $roles = get_editable_roles(); if ( ! $with_admin ) { unset( $roles['administrator'] ); } elseif ( isset( $roles['administrator'] ) ) { $roles = array( 'administrator' => $roles['administrator'] ) + $roles; } return $roles; } private function role_name( $key, $data = null ) { if ( null === $data ) { $all = wp_roles()->roles; $data = isset( $all[ $key ] ) ? $all[ $key ] : array(); } return isset( $data['name'] ) ? translate_user_role( $data['name'] ) : $key; } private function role_user_counts() { $counts = count_users(); return isset( $counts['avail_roles'] ) ? $counts['avail_roles'] : array(); } private function initial( $text ) { $text = trim( (string) $text ); if ( '' === $text ) { return '?'; } return function_exists( 'mb_substr' ) ? mb_strtoupper( mb_substr( $text, 0, 1 ) ) : strtoupper( substr( $text, 0, 1 ) ); } /* ===================================================================== * Menu slugs * ===================================================================== */ /** * Normalizes a slug for comparisons (the Customizer adds a "return" * parameter that changes on every page). */ public function normalize_slug( $slug ) { $slug = html_entity_decode( (string) $slug, ENT_QUOTES, 'UTF-8' ); if ( false !== strpos( $slug, 'return=' ) && false !== strpos( $slug, '?' ) ) { list( $file, $query ) = explode( '?', $slug, 2 ); $args = array(); parse_str( $query, $args ); unset( $args['return'] ); $slug = $file . ( $args ? '?' . http_build_query( $args ) : '' ); } return $slug; } private function clean_slug( $value ) { if ( ! is_string( $value ) ) { return ''; } $value = trim( wp_kses_no_null( wp_strip_all_tags( $value ) ) ); return '' === $value ? '' : $this->normalize_slug( $value ); } /** * Parses a menu slug: either a plugin page (page) or a file + query args. */ private function parse_slug( $slug ) { if ( preg_match( '#^https?://#i', $slug ) ) { return null; } $parts = explode( '?', $slug, 2 ); $file = $parts[0]; $args = array(); if ( isset( $parts[1] ) ) { parse_str( $parts[1], $args ); } if ( false === strpos( $file, '.php' ) ) { return array( 'page' => $slug ); } if ( isset( $args['page'] ) && is_string( $args['page'] ) ) { return array( 'page' => $args['page'] ); } if ( in_array( $file, array( 'edit.php', 'post-new.php' ), true ) && ! isset( $args['post_type'] ) ) { $args['post_type'] = 'post'; } return array( 'file' => $file, 'args' => $args, ); } private function is_separator( $item ) { return isset( $item[4] ) && false !== strpos( (string) $item[4], 'wp-menu-separator' ); } private function clean_title( $title, $fallback ) { $title = preg_replace( '#\s*]*class=["\'][^"\']*(?:update-plugins|awaiting-mod|menu-counter|count-|pending-count|plugin-count)[^"\']*["\'][^>]*>.*$#is', '', (string) $title ); $title = trim( wp_strip_all_tags( $title ) ); return '' !== $title ? $title : $fallback; } /** * Current menu structure (administrator view) for the settings screen. */ private function get_menu_structure() { global $menu, $submenu; $out = array(); $exclude = array( self::PAGE, self::PAGE_LEGACY ); if ( ! is_array( $menu ) ) { return $out; } foreach ( $menu as $item ) { if ( empty( $item[2] ) || $this->is_separator( $item ) || in_array( $item[2], $exclude, true ) ) { continue; } $slug = $item[2]; $entry = array( 'title' => $this->clean_title( $item[0], $slug ), 'slug' => $slug, 'icon' => isset( $item[6] ) ? (string) $item[6] : '', 'cap' => isset( $item[1] ) ? $item[1] : '', 'submenu' => array(), ); if ( ! empty( $submenu[ $slug ] ) && is_array( $submenu[ $slug ] ) ) { foreach ( $submenu[ $slug ] as $sub ) { if ( empty( $sub[2] ) || in_array( $sub[2], $exclude, true ) ) { continue; } $entry['submenu'][] = array( 'title' => $this->clean_title( $sub[0], $sub[2] ), 'slug' => $sub[2], 'cap' => isset( $sub[1] ) ? $sub[1] : '', ); } } $out[] = $entry; } return $out; } /* ===================================================================== * Capabilities required by menu items * ===================================================================== */ /** * Capabilities never granted automatically without explicit consent. */ public function sensitive_caps() { return array( 'manage_options', 'activate_plugins', 'install_plugins', 'update_plugins', 'delete_plugins', 'edit_plugins', 'upload_plugins', 'install_themes', 'update_themes', 'delete_themes', 'edit_themes', 'upload_themes', 'switch_themes', 'update_core', 'update_languages', 'install_languages', 'edit_files', 'list_users', 'create_users', 'edit_users', 'delete_users', 'promote_users', 'remove_users', 'add_users', 'unfiltered_html', 'unfiltered_upload', 'import', 'export', 'manage_network', 'manage_sites', 'manage_network_users', 'manage_network_plugins', 'manage_network_themes', 'manage_network_options', 'setup_network', 'upgrade_network', 'delete_site', ); } /** * Capabilities a role needs for a menu item to be displayed AND usable * (e.g. Pages → edit_pages, publish_pages, delete_pages…). */ private function caps_for_item( $slug, $menu_cap ) { $caps = array(); $parsed = $this->parse_slug( $this->normalize_slug( $slug ) ); if ( $parsed && isset( $parsed['file'] ) ) { $file = $parsed['file']; $args = $parsed['args']; if ( ( 'edit.php' === $file || 'post-new.php' === $file ) && ! empty( $args['post_type'] ) && is_string( $args['post_type'] ) ) { $pto = get_post_type_object( $args['post_type'] ); if ( $pto ) { foreach ( (array) $pto->cap as $key => $cap ) { if ( ! in_array( $key, array( 'edit_post', 'read_post', 'delete_post', 'read' ), true ) ) { $caps[] = $cap; } } } } elseif ( 'edit-tags.php' === $file && ! empty( $args['taxonomy'] ) && is_string( $args['taxonomy'] ) ) { $tax = get_taxonomy( $args['taxonomy'] ); if ( $tax ) { $caps = array_merge( $caps, array_values( (array) $tax->cap ) ); } } elseif ( 'edit-comments.php' === $file ) { $caps[] = 'moderate_comments'; } } if ( is_string( $menu_cap ) && '' !== $menu_cap ) { $caps[] = $menu_cap; } $out = array(); $uid = get_current_user_id(); $roles = wp_roles(); foreach ( $caps as $cap ) { if ( ! is_string( $cap ) || '' === $cap ) { continue; } foreach ( (array) map_meta_cap( $cap, $uid ) as $primitive ) { if ( ! is_string( $primitive ) || in_array( $primitive, array( 'read', 'exist', 'do_not_allow' ), true ) || $roles->is_role( $primitive ) ) { continue; } $out[] = $primitive; } } return array_values( array_unique( $out ) ); } /** * Computes (and stores for saving) the capabilities of every menu item. */ private function build_caps_map( $structure ) { $map = array(); foreach ( $structure as $parent ) { $map[ $this->normalize_slug( $parent['slug'] ) ] = $this->caps_for_item( $parent['slug'], $parent['cap'] ); foreach ( $parent['submenu'] as $sub ) { $key = $this->normalize_slug( $sub['slug'] ); $map[ $key ] = isset( $map[ $key ] ) ? array_values( array_unique( array_merge( $map[ $key ], $this->caps_for_item( $sub['slug'], $sub['cap'] ) ) ) ) : $this->caps_for_item( $sub['slug'], $sub['cap'] ); } } if ( get_option( self::OPT_CAPS ) !== $map ) { update_option( self::OPT_CAPS, $map, false ); } return $map; } private function get_granted() { $granted = get_option( self::OPT_GRANTED, array() ); return is_array( $granted ) ? $granted : array(); } /** * Grants the role the capabilities of the checked items and removes the ones the plugin * added earlier that are no longer needed. The role's original capabilities are never removed. */ private function sync_role_caps( $role_key, $slugs, $with_sensitive ) { $role = get_role( $role_key ); if ( ! $role || 'administrator' === $role_key ) { return; } $map = get_option( self::OPT_CAPS, array() ); $needed = array(); foreach ( $slugs as $slug ) { if ( isset( $map[ $slug ] ) ) { $needed = array_merge( $needed, (array) $map[ $slug ] ); } } $needed = array_unique( $needed ); if ( ! $with_sensitive ) { $needed = array_diff( $needed, $this->sensitive_caps() ); } $all = $this->get_granted(); $granted = isset( $all[ $role_key ] ) ? (array) $all[ $role_key ] : array(); foreach ( array_diff( $granted, $needed ) as $cap ) { $role->remove_cap( $cap ); } $keep = array_values( array_intersect( $granted, $needed ) ); foreach ( $needed as $cap ) { if ( empty( $role->capabilities[ $cap ] ) ) { $role->add_cap( $cap ); $keep[] = $cap; } } $all[ $role_key ] = array_values( array_unique( $keep ) ); update_option( self::OPT_GRANTED, $all, false ); } /* ===================================================================== * Menu filtering + direct access blocking * ===================================================================== */ public function filter_admin_menu_for_roles() { if ( ! is_admin() || wp_doing_ajax() ) { return; } $roles = $this->user_roles(); if ( empty( $roles ) || $this->is_admin_user() ) { return; // Administrators always see everything. } $settings = get_option( self::OPT_MENU, array() ); $allowed = array(); foreach ( $roles as $role ) { $cfg = $this->role_cfg( $role ); if ( empty( $cfg['restrict_menu'] ) ) { return; // One of the roles is not restricted: full menu. } if ( ! empty( $settings[ $role ] ) && is_array( $settings[ $role ] ) ) { $allowed = array_merge( $allowed, $settings[ $role ] ); } } $allowed = array_values( array_unique( array_map( array( $this, 'normalize_slug' ), $allowed ) ) ); $this->allowed = $allowed; global $menu, $submenu; // Snapshot of the menu before filtering (used to block direct links). $this->menu_snapshot = array(); if ( is_array( $menu ) ) { foreach ( $menu as $item ) { if ( ! empty( $item[2] ) && ! $this->is_separator( $item ) ) { $this->add_to_snapshot( $item[2] ); } } } if ( is_array( $submenu ) ) { foreach ( $submenu as $items ) { foreach ( (array) $items as $sub ) { if ( ! empty( $sub[2] ) ) { $this->add_to_snapshot( $sub[2] ); } } } } // 1. Submenus. if ( is_array( $submenu ) ) { foreach ( $submenu as $parent_slug => $items ) { if ( ! is_array( $items ) ) { continue; } foreach ( $items as $index => $sub ) { if ( empty( $sub[2] ) ) { continue; } if ( ! in_array( $this->normalize_slug( $sub[2] ), $allowed, true ) ) { unset( $submenu[ $parent_slug ][ $index ] ); } } if ( empty( $submenu[ $parent_slug ] ) ) { unset( $submenu[ $parent_slug ] ); } } } // 2. Top-level menus (separators are kept). if ( is_array( $menu ) ) { foreach ( $menu as $index => $item ) { if ( empty( $item[2] ) || $this->is_separator( $item ) ) { continue; } if ( ! in_array( $this->normalize_slug( $item[2] ), $allowed, true ) ) { unset( $menu[ $index ] ); } } } } private function add_to_snapshot( $slug ) { $norm = $this->normalize_slug( $slug ); if ( isset( $this->menu_snapshot[ $norm ] ) ) { return; } $parsed = $this->parse_slug( $norm ); if ( $parsed ) { $this->menu_snapshot[ $norm ] = $parsed; } } /** * Menu pages equivalent to the current request (e.g. post.php → edit.php?post_type=…). */ private function request_candidates( $file, $args ) { switch ( $file ) { case 'post.php': $post_id = 0; if ( isset( $_GET['post'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification $post_id = (int) $_GET['post']; // phpcs:ignore WordPress.Security.NonceVerification } elseif ( isset( $_POST['post_ID'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification $post_id = (int) $_POST['post_ID']; // phpcs:ignore WordPress.Security.NonceVerification } $type = $post_id ? get_post_type( $post_id ) : ''; return array( array( 'edit.php', array( 'post_type' => $type ? $type : 'post' ) ) ); case 'post-new.php': $type = ! empty( $args['post_type'] ) ? $args['post_type'] : 'post'; return array( array( 'post-new.php', array_merge( $args, array( 'post_type' => $type ) ) ), array( 'edit.php', array( 'post_type' => $type ) ), ); case 'edit.php': if ( empty( $args['post_type'] ) ) { $args['post_type'] = 'post'; } return array( array( 'edit.php', $args ) ); case 'term.php': case 'edit-tags.php': return array( array( 'edit-tags.php', $args ) ); case 'user-edit.php': return array( array( 'users.php', array() ) ); case 'user-new.php': return array( array( 'user-new.php', $args ), array( 'users.php', array() ) ); case 'comment.php': return array( array( 'edit-comments.php', array() ) ); case 'media-new.php': return array( array( 'media-new.php', $args ), array( 'upload.php', array() ) ); case 'theme-install.php': return array( array( 'theme-install.php', $args ), array( 'themes.php', array() ) ); case 'plugin-install.php': return array( array( 'plugin-install.php', $args ), array( 'plugins.php', array() ) ); } return array( array( $file, $args ) ); } /** * Finds the menu item matching the current request (null if none). */ private function match_request() { global $pagenow, $plugin_page; if ( ! empty( $plugin_page ) ) { foreach ( $this->menu_snapshot as $slug => $parsed ) { if ( $slug === $plugin_page || ( isset( $parsed['page'] ) && $parsed['page'] === $plugin_page ) ) { return $slug; } } return null; } $args = array(); foreach ( $_GET as $key => $value ) { // phpcs:ignore WordPress.Security.NonceVerification if ( is_string( $value ) ) { $args[ $key ] = wp_unslash( $value ); } } unset( $args['return'] ); foreach ( $this->request_candidates( $pagenow, $args ) as $candidate ) { list( $file, $cargs ) = $candidate; $best = null; $best_score = -1; foreach ( $this->menu_snapshot as $slug => $parsed ) { if ( ! isset( $parsed['file'] ) || $parsed['file'] !== $file ) { continue; } $ok = true; foreach ( $parsed['args'] as $k => $v ) { if ( ! isset( $cargs[ $k ] ) || (string) $cargs[ $k ] !== (string) $v ) { $ok = false; break; } } if ( $ok && count( $parsed['args'] ) > $best_score ) { $best = $slug; $best_score = count( $parsed['args'] ); } } if ( null !== $best ) { return $best; } } return null; } public function enforce_direct_access() { if ( null === $this->allowed || wp_doing_ajax() ) { return; } $general = $this->get_general(); if ( empty( $general['block_direct_access'] ) ) { return; } global $pagenow, $plugin_page; if ( in_array( $pagenow, array( 'admin-ajax.php', 'admin-post.php', 'async-upload.php' ), true ) ) { return; } $slug = $this->match_request(); if ( null === $slug || in_array( $slug, $this->allowed, true ) ) { return; } // Hidden Dashboard: redirect to the first allowed page (e.g. after login). if ( 'index.php' === $pagenow && empty( $plugin_page ) ) { wp_safe_redirect( $this->first_allowed_url() ); exit; } $back = $this->first_allowed_url(); wp_die( '

' . esc_html__( 'Access denied', 'cc-admin-menu-control' ) . '

' . '

' . esc_html__( 'You do not have access to this page.', 'cc-admin-menu-control' ) . '

' . '

' . esc_html__( 'Go back', 'cc-admin-menu-control' ) . '

', esc_html__( 'Access denied', 'cc-admin-menu-control' ), array( 'response' => 403 ) ); } private function first_allowed_url() { global $menu, $submenu; if ( is_array( $menu ) ) { foreach ( $menu as $item ) { if ( empty( $item[2] ) || $this->is_separator( $item ) ) { continue; } if ( isset( $item[1] ) && ! current_user_can( $item[1] ) ) { continue; } $parent = $item[2]; $target = $parent; if ( ! empty( $submenu[ $parent ] ) ) { $first = reset( $submenu[ $parent ] ); if ( ! empty( $first[2] ) ) { $target = $first[2]; } } if ( 'index.php' === $target ) { continue; } return $this->menu_item_url( $target, $parent ); } } return home_url( '/' ); } private function menu_item_url( $slug, $parent ) { if ( preg_match( '#^https?://#i', $slug ) ) { return $slug; } if ( get_plugin_page_hook( $slug, $parent ) ) { $base = ( false !== strpos( $parent, '.php' ) ) ? $parent : 'admin.php'; return add_query_arg( 'page', $slug, admin_url( $base ) ); } return admin_url( $slug ); } /* ===================================================================== * Admin access * ===================================================================== */ private function is_user_admin_blocked( $user = null ) { $user = $user ? $user : wp_get_current_user(); if ( ! $user || ! $user->exists() ) { return false; } // Safety: administrators are never blocked (prevents lock-outs). if ( $this->is_admin_user( $user ) ) { return false; } if ( get_user_meta( $user->ID, self::META_BLOCK, true ) ) { return true; } return $this->role_flag_all( 'block_admin', $user ); } private function blocked_redirect_url() { $general = $this->get_general(); $url = home_url( '/' ); if ( 'account' === $general['redirect'] && function_exists( 'wc_get_page_permalink' ) ) { $url = wc_get_page_permalink( 'myaccount' ); } elseif ( 'custom' === $general['redirect'] && ! empty( $general['redirect_url'] ) ) { $url = $general['redirect_url']; } $url = wp_validate_redirect( $url, home_url( '/' ) ); // Loop guard: a blocked account cannot be redirected to the admin area. if ( $this->is_admin_url( $url ) ) { $url = home_url( '/' ); } return $url; } private function is_admin_url( $url ) { $path = (string) wp_parse_url( $url, PHP_URL_PATH ); $admin_path = (string) wp_parse_url( admin_url(), PHP_URL_PATH ); return '' !== $admin_path && 0 === strpos( trailingslashit( $path ), $admin_path ); } /* --------------------------------------------------------------------- * Dashboard home screen hidden (the admin area stays available) * ------------------------------------------------------------------- */ private function dashboard_hidden_for_user( $user = null ) { return ! $this->is_admin_user( $user ) && $this->role_flag_all( 'hide_dashboard', $user ); } /** * After login, WordPress sends some roles to "Profile": go through the * dashboard instead so the role is redirected to its landing page. */ public function filter_login_redirect( $redirect_to, $requested, $user ) { if ( ! ( $user instanceof WP_User ) || ! $user->exists() || ! $this->dashboard_hidden_for_user( $user ) || $this->is_user_admin_blocked( $user ) ) { return $redirect_to; } if ( '' === (string) $requested || admin_url() === $requested || admin_url() === $redirect_to || admin_url( 'profile.php' ) === $redirect_to ) { // Explicit "index.php": otherwise WordPress replaces admin_url() with profile.php right after this filter. return admin_url( 'index.php' ); } return $redirect_to; } public function hide_dashboard_menu() { if ( ! is_admin() || wp_doing_ajax() || ! $this->dashboard_hidden_for_user() ) { return; } global $menu, $submenu; unset( $submenu['index.php'] ); if ( is_array( $menu ) ) { foreach ( $menu as $i => $item ) { if ( isset( $item[2] ) && 'index.php' === $item[2] ) { unset( $menu[ $i ] ); } } } } public function enforce_hidden_dashboard() { global $pagenow, $plugin_page; if ( wp_doing_ajax() || 'index.php' !== $pagenow || ! empty( $plugin_page ) || ! $this->dashboard_hidden_for_user() ) { return; } wp_safe_redirect( $this->landing_url() ); exit; } /** * Landing page chosen for the role (if the user can access it), otherwise the first allowed page. */ private function landing_url() { global $menu, $submenu; foreach ( $this->user_roles() as $role ) { $cfg = $this->role_cfg( $role ); $slug = $cfg['landing']; if ( '' === $slug || 'index.php' === $slug ) { continue; } if ( is_array( $submenu ) ) { foreach ( $submenu as $parent => $items ) { foreach ( (array) $items as $item ) { if ( isset( $item[2] ) && $this->normalize_slug( $item[2] ) === $slug && current_user_can( $item[1] ) ) { return $this->menu_item_url( $item[2], $parent ); } } } } if ( is_array( $menu ) ) { foreach ( $menu as $item ) { if ( isset( $item[2] ) && $this->normalize_slug( $item[2] ) === $slug && current_user_can( $item[1] ) ) { return $this->menu_item_url( $item[2], $item[2] ); } } } } return $this->first_allowed_url(); } public function enforce_admin_block() { if ( wp_doing_ajax() || ( defined( 'DOING_CRON' ) && DOING_CRON ) ) { return; } global $pagenow; if ( in_array( $pagenow, array( 'admin-ajax.php', 'admin-post.php', 'async-upload.php' ), true ) ) { return; } if ( ! $this->is_user_admin_blocked() ) { return; } wp_safe_redirect( $this->blocked_redirect_url() ); exit; } public function filter_show_admin_bar( $show ) { if ( is_admin() || ! is_user_logged_in() ) { return $show; } if ( $this->is_user_admin_blocked() || $this->role_flag_all( 'hide_admin_bar' ) ) { return false; } return $show; } /* ===================================================================== * Notifications & WordPress info * ===================================================================== */ private function iface() { if ( null === $this->iface ) { $this->iface = array( 'plugins' => $this->role_flag_all( 'hide_plugin_notices' ), 'wp' => $this->role_flag_all( 'hide_wp_info' ), ); } return $this->iface; } public function maybe_disable_pointers() { $iface = $this->iface(); if ( $iface['plugins'] || $iface['wp'] ) { remove_action( 'admin_enqueue_scripts', array( 'WP_Internal_Pointers', 'enqueue_scripts' ) ); } } /** * Does a callback come from WordPress core (wp-admin / wp-includes)? */ private function is_core_callback( $fn ) { try { if ( is_string( $fn ) && false !== strpos( $fn, '::' ) ) { $fn = explode( '::', $fn, 2 ); } if ( is_array( $fn ) && 2 === count( $fn ) ) { $ref = new ReflectionMethod( $fn[0], $fn[1] ); } elseif ( is_string( $fn ) || $fn instanceof Closure ) { $ref = new ReflectionFunction( $fn ); } elseif ( is_object( $fn ) && method_exists( $fn, '__invoke' ) ) { $ref = new ReflectionMethod( $fn, '__invoke' ); } else { return false; } $file = $ref->getFileName(); if ( ! $file ) { return true; // Internal PHP function. } $file = wp_normalize_path( $file ); $abs = trailingslashit( wp_normalize_path( ABSPATH ) ); return 0 === strpos( $file, $abs . WPINC . '/' ) || 0 === strpos( $file, $abs . 'wp-admin/' ); } catch ( Exception $e ) { return false; } } public function strip_admin_notices() { $iface = $this->iface(); if ( ! $iface['plugins'] && ! $iface['wp'] ) { return; } global $wp_filter; foreach ( array( 'admin_notices', 'all_admin_notices', 'network_admin_notices', 'user_admin_notices' ) as $hook ) { if ( empty( $wp_filter[ $hook ] ) || ! is_object( $wp_filter[ $hook ] ) ) { continue; } $callbacks = $wp_filter[ $hook ]->callbacks; foreach ( $callbacks as $priority => $items ) { foreach ( $items as $item ) { $core = $this->is_core_callback( $item['function'] ); if ( ( $core && $iface['wp'] ) || ( ! $core && $iface['plugins'] ) ) { remove_action( $hook, $item['function'], $priority ); } } } } } public function interface_styles() { $iface = $this->iface(); if ( ! $iface['plugins'] && ! $iface['wp'] ) { return; } $css = '.wp-pointer{display:none!important;}'; if ( $iface['plugins'] ) { $css .= '.fs-notice,.fs-sticky,#wpbody-content>.notice:not(.inline):not(.settings-error):not(#message),#wpbody-content>.updated:not(.inline):not(#message){display:none!important;}'; } if ( $iface['wp'] ) { $css .= '.update-nag,#welcome-panel,#wp-admin-bar-wp-logo,#contextual-help-link-wrap,#footer-thankyou,#footer-upgrade{display:none!important;}'; } wp_register_style( 'ccamc-interface', false, array(), CCAMC_VERSION ); wp_enqueue_style( 'ccamc-interface' ); wp_add_inline_style( 'ccamc-interface', $css ); } public function interface_help_tabs() { $iface = $this->iface(); $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null; if ( $iface['wp'] && $screen ) { $screen->remove_help_tabs(); $screen->set_help_sidebar( '' ); } } public function remove_dashboard_widgets() { $iface = $this->iface(); if ( ! $iface['wp'] ) { return; } remove_action( 'welcome_panel', 'wp_welcome_panel' ); remove_meta_box( 'dashboard_primary', 'dashboard', 'side' ); // WordPress Events and News. remove_meta_box( 'dashboard_site_health', 'dashboard', 'normal' ); // Site Health Status. remove_meta_box( 'dashboard_php_nag', 'dashboard', 'normal' ); remove_meta_box( 'dashboard_browser_nag', 'dashboard', 'normal' ); } public function clean_admin_bar( $bar ) { if ( is_object( $bar ) && $this->dashboard_hidden_for_user() ) { $bar->remove_node( 'dashboard' ); } $iface = $this->iface(); if ( $iface['wp'] && is_object( $bar ) ) { $bar->remove_node( 'wp-logo' ); $bar->remove_node( 'updates' ); } } public function filter_footer_text( $text ) { $iface = $this->iface(); return $iface['wp'] ? '' : $text; } /* ===================================================================== * User profile * ===================================================================== */ public function render_profile_field( $user ) { if ( ! current_user_can( 'manage_options' ) || $this->is_admin_user( $user ) ) { return; } $blocked = (bool) get_user_meta( $user->ID, self::META_BLOCK, true ); ?>

is_admin_user( get_userdata( $user_id ) ) || empty( $_POST['ccamc_block_admin'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification delete_user_meta( $user_id, self::META_BLOCK ); } else { update_user_meta( $user_id, self::META_BLOCK, 1 ); } } /* ===================================================================== * Pages & assets * ===================================================================== */ public function register_pages() { $cb = array( $this, 'render_page' ); $this->page_hooks[] = add_menu_page( __( 'CC Admin Menu Control', 'cc-admin-menu-control' ), __( 'Admin Menu Control', 'cc-admin-menu-control' ), 'manage_options', self::PAGE, $cb, 'dashicons-visibility', 71 ); $this->page_hooks[] = add_submenu_page( self::PAGE, __( 'Menu by role', 'cc-admin-menu-control' ), __( 'Menu by role', 'cc-admin-menu-control' ), 'manage_options', self::PAGE, $cb ); $this->page_hooks[] = add_submenu_page( self::PAGE, __( 'Interface & notifications', 'cc-admin-menu-control' ), __( 'Notifications', 'cc-admin-menu-control' ), 'manage_options', self::PAGE_INTERFACE, $cb ); $this->page_hooks[] = add_submenu_page( self::PAGE, __( 'Admin access', 'cc-admin-menu-control' ), __( 'Access', 'cc-admin-menu-control' ), 'manage_options', self::PAGE_ACCESS, $cb ); $this->page_hooks[] = add_submenu_page( self::PAGE, __( 'Roles', 'cc-admin-menu-control' ), __( 'Roles', 'cc-admin-menu-control' ), 'manage_options', self::PAGE_ROLES, $cb ); // Shortcut under Settings. $this->page_hooks[] = add_options_page( __( 'CC Admin Menu Control', 'cc-admin-menu-control' ), __( 'Menu by role', 'cc-admin-menu-control' ), 'manage_options', self::PAGE_LEGACY, $cb ); } public function enqueue_assets( $hook ) { if ( ! in_array( $hook, $this->page_hooks, true ) ) { return; } // Version = plugin version + file date, so browsers never keep an outdated copy. $dir = plugin_dir_path( CCAMC_FILE ); wp_enqueue_style( 'ccamc-admin', CCAMC_URL . 'assets/css/admin.css', array(), CCAMC_VERSION . '.' . filemtime( $dir . 'assets/css/admin.css' ) ); wp_enqueue_script( 'ccamc-admin', CCAMC_URL . 'assets/js/admin.js', array(), CCAMC_VERSION . '.' . filemtime( $dir . 'assets/js/admin.js' ), true ); wp_localize_script( 'ccamc-admin', 'CCAMC', array( 'confirmDelete' => /* translators: %s: role name. */ __( 'Permanently delete the “%s” role? This cannot be undone.', 'cc-admin-menu-control' ), 'unsaved' => __( 'Some changes have not been saved.', 'cc-admin-menu-control' ), 'full' => __( 'Full menu', 'cc-admin-menu-control' ), 'none' => __( 'No visible item', 'cc-admin-menu-control' ), 'visible' => /* translators: 1: number of checked menu items, 2: total number of menu items. */ __( '%1$d / %2$d visible items', 'cc-admin-menu-control' ), 'copySuffix' => __( ' (copy)', 'cc-admin-menu-control' ), 'sensitive' => $this->sensitive_caps(), 'willAdd' => /* translators: %s: list of capabilities. */ __( 'When saved, this role will receive: %s', 'cc-admin-menu-control' ), 'willRemove' => /* translators: %s: list of capabilities. */ __( 'Capabilities added by the plugin that will be removed: %s', 'cc-admin-menu-control' ), 'blocked' => /* translators: %s: list of capabilities. */ __( 'Sensitive capabilities not granted (tick the option above if needed): %s', 'cc-admin-menu-control' ), 'upToDate' => __( 'The role already has every capability needed by the checked items.', 'cc-admin-menu-control' ), 'missingOff' => /* translators: %d: number of menu items. */ __( '%d checked item(s) will not be displayed: this role lacks the required capability. Enable the option above.', 'cc-admin-menu-control' ), 'permBadge' => /* translators: %d: number of menu items. */ __( '%d without capability', 'cc-admin-menu-control' ), ) ); } /** * Links to the author's other plugins, under the plugin row on the Plugins screen. */ public function row_meta( $links, $file ) { if ( plugin_basename( CCAMC_FILE ) !== $file ) { return $links; } $links[] = '' . esc_html__( 'More plugins by CC ADDONS', 'cc-admin-menu-control' ) . ''; $links[] = '' . esc_html__( 'Rate this plugin ★★★★★', 'cc-admin-menu-control' ) . ''; return $links; } /** * Footer text on the plugin's own screens only. */ public function plugin_footer_text( $text ) { $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null; if ( ! $screen || ! in_array( $screen->id, $this->page_hooks, true ) ) { return $text; } return sprintf( /* translators: 1: plugin name, 2: link to the review form, 3: link to the author's website. */ __( 'Thank you for using %1$s! A %2$s review helps a lot · More plugins on %3$s', 'cc-admin-menu-control' ), 'CC Admin Menu Control', '★★★★★', 'cc-addons.com' ); } private function ccaddons_url( $path, $medium ) { return add_query_arg( array( 'utm_source' => 'cc-admin-menu-control', 'utm_medium' => $medium, 'utm_campaign' => 'plugin', ), 'https://cc-addons.com/' . ltrim( $path, '/' ) ); } /** * "More from CC ADDONS" block, shown at the bottom of the plugin's own screens only * (static content: nothing is loaded from an external server). */ private function render_more_plugins() { $pdf_active = defined( 'CCVPB_FILE' ) || class_exists( 'CCVPB_Admin' ); $can_install = current_user_can( 'install_plugins' ); $search_url = admin_url( 'plugin-install.php?s=' . rawurlencode( 'CC Visual PDF Builder' ) . '&tab=search&type=term' ); $products = array( array( 'icon' => 'dashicons-media-document', 'name' => 'CC Visual PDF Builder', 'badge' => __( 'Free', 'cc-admin-menu-control' ), 'desc' => __( 'Design PDF documents with a drag & drop builder and attach them automatically to your form e-mails (Contact Form 7, Elementor, WPForms).', 'cc-admin-menu-control' ), 'done' => $pdf_active, 'label' => $can_install ? __( 'Install for free', 'cc-admin-menu-control' ) : __( 'Learn more', 'cc-admin-menu-control' ), 'url' => $can_install ? $search_url : $this->ccaddons_url( '', 'more-plugins' ), 'ext' => ! $can_install, ), array( 'icon' => 'dashicons-star-filled', 'name' => 'CC Visual PDF Builder PRO', 'badge' => 'Premium', 'desc' => __( 'Unlimited and ready-made templates, Google Fonts, dynamic QR codes, WooCommerce invoices and bulk PDF generation.', 'cc-admin-menu-control' ), 'done' => false, 'label' => __( 'Discover PRO', 'cc-admin-menu-control' ), 'url' => $this->ccaddons_url( 'produit/cc-visual-pdf-builder-pro/', 'more-plugins' ), 'ext' => true, ), ); ?>

' . esc_html__( 'Settings', 'cc-admin-menu-control' ) . '' ); return $links; } private function current_tab() { $page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : self::PAGE; // phpcs:ignore WordPress.Security.NonceVerification $map = array( self::PAGE => 'menu', self::PAGE_LEGACY => 'menu', self::PAGE_INTERFACE => 'interface', self::PAGE_ACCESS => 'access', self::PAGE_ROLES => 'roles', ); return isset( $map[ $page ] ) ? $map[ $page ] : 'menu'; } public function render_page() { if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'cc-admin-menu-control' ) ); } $tab = $this->current_tab(); $tabs = array( 'menu' => array( self::PAGE, __( 'Menu by role', 'cc-admin-menu-control' ), 'dashicons-menu-alt3' ), 'interface' => array( self::PAGE_INTERFACE, __( 'Notifications & info', 'cc-admin-menu-control' ), 'dashicons-megaphone' ), 'access' => array( self::PAGE_ACCESS, __( 'Dashboard access', 'cc-admin-menu-control' ), 'dashicons-lock' ), 'roles' => array( self::PAGE_ROLES, __( 'Roles', 'cc-admin-menu-control' ), 'dashicons-groups' ), ); ?>


render_messages(); ?> render_tab_interface(); break; case 'access': $this->render_tab_access(); break; case 'roles': $this->render_tab_roles(); break; default: $this->render_tab_menu(); } $this->render_more_plugins(); ?>
array( 'success', __( 'Settings saved.', 'cc-admin-menu-control' ) ), 'role_created' => array( 'success', sprintf( /* translators: %s: role name. */ __( 'The “%s” role has been created.', 'cc-admin-menu-control' ), $name ) ), 'role_deleted' => array( 'success', $num ? sprintf( /* translators: 1: role name, 2: number of users. */ _n( 'The “%1$s” role has been deleted. %2$d user has been reassigned.', 'The “%1$s” role has been deleted. %2$d users have been reassigned.', $num, 'cc-admin-menu-control' ), $name, $num ) : sprintf( /* translators: %s: role name. */ __( 'The “%s” role has been deleted.', 'cc-admin-menu-control' ), $name ) ), 'role_exists' => array( 'error', __( 'A role with this identifier already exists.', 'cc-admin-menu-control' ) ), 'role_invalid' => array( 'error', __( 'Please enter a valid role name.', 'cc-admin-menu-control' ) ), 'role_protected' => array( 'error', __( 'This role cannot be deleted.', 'cc-admin-menu-control' ) ), 'user_blocked' => array( 'success', sprintf( /* translators: %s: user display name. */ __( 'Admin access is now blocked for %s.', 'cc-admin-menu-control' ), $name ) ), 'user_unblocked' => array( 'success', sprintf( /* translators: %s: user display name. */ __( 'Admin access has been restored for %s.', 'cc-admin-menu-control' ), $name ) ), 'user_invalid' => array( 'error', __( 'This account cannot be blocked.', 'cc-admin-menu-control' ) ), 'redirect_admin' => array( 'error', __( 'Settings saved, but the redirect address pointed to the admin area (which creates a loop): it has been replaced by the site home page. To send a role straight to an admin page, choose “Without the dashboard home screen” and its landing page.', 'cc-admin-menu-control' ) ), ); if ( ! isset( $messages[ $code ] ) ) { return; } list( $type, $text ) = $messages[ $code ]; $icon = 'success' === $type ? 'dashicons-yes-alt' : 'dashicons-warning'; printf( '
%3$s
', esc_attr( $type ), esc_attr( $icon ), esc_html( $text ) ); } private function render_switch( $name, $checked, $label = '', $args = array() ) { $args = wp_parse_args( $args, array( 'class' => '', 'disabled' => false, 'input' => '', 'sr' => '', ) ); ?> '; } elseif ( preg_match( '#^data:image/svg\+xml;base64,[A-Za-z0-9+/=]+$#', $icon ) || preg_match( '#^https?://#i', $icon ) ) { echo ''; } else { echo ''; } } private function form_open( $action, $attrs = array() ) { echo '
$value ) { echo ' ' . esc_attr( $attr ) . '="' . esc_attr( $value ) . '"'; } echo '>'; echo ''; wp_nonce_field( 'ccamc_' . $action, 'ccamc_nonce' ); } private function save_bar( $label = '' ) { ?>
get_roles( false ); $structure = $this->get_menu_structure(); $settings = get_option( self::OPT_MENU, array() ); $general = $this->get_general(); $counts = $this->role_user_counts(); $caps_map = $this->build_caps_map( $structure ); $granted = $this->get_granted(); $sensitive = $this->sensitive_caps(); $this->form_open( 'save_menu', array( 'id' => 'ccamc-menu-form', 'data-ccamc-dirty' => '1' ) ); ?>

render_switch( 'ccamc_block_direct', ! empty( $general['block_direct_access'] ), '', array( 'sr' => __( 'Block access through direct links', 'cc-admin-menu-control' ) ) ); ?>

$role_data ) : $name = $this->role_name( $role_key, $role_data ); $cfg = $this->role_cfg( $role_key ); $restricted = ! empty( $cfg['restrict_menu'] ); $checked = isset( $settings[ $role_key ] ) ? array_map( array( $this, 'normalize_slug' ), (array) $settings[ $role_key ] ) : array(); $n_users = isset( $counts[ $role_key ] ) ? (int) $counts[ $role_key ] : 0; $panel_id = 'ccamc-panel-' . $role_key; $field = 'ccamc_settings[' . $role_key . '][]'; $role_caps = isset( $role_data['capabilities'] ) ? (array) $role_data['capabilities'] : array(); $cb_attrs = function ( $value ) use ( $caps_map, $role_caps ) { $caps = isset( $caps_map[ $value ] ) ? $caps_map[ $value ] : array(); $missing = array(); foreach ( $caps as $cap ) { if ( empty( $role_caps[ $cap ] ) ) { $missing[] = $cap; } } return array( $caps, $missing ); }; ?>
render_switch( 'ccamc_restrict[' . $role_key . ']', $restricted, __( 'Restrict', 'cc-admin-menu-control' ), array( 'input' => 'ccamc-restrict-toggle' ) ); ?>
save_bar(); ?>
%2$s', $is_sensitive ? ' ccamc-lock--sensitive' : '', esc_attr( sprintf( /* translators: %s: list of capabilities. */ __( 'This role lacks the capability: %s', 'cc-admin-menu-control' ), implode( ', ', $missing ) ) ) ); } /* --------------------------------------------------------------------- * Tab: Notifications & info * ------------------------------------------------------------------- */ private function render_tab_interface() { $roles = $this->get_roles( true ); $counts = $this->role_user_counts(); $me = $this->user_roles(); $this->form_open( 'save_interface', array( 'data-ccamc-dirty' => '1' ) ); ?>

$role_data ) : $name = $this->role_name( $role_key, $role_data ); $cfg = $this->role_cfg( $role_key ); ?>
role_cell( $role_key, $name, isset( $counts[ $role_key ] ) ? (int) $counts[ $role_key ] : 0, in_array( $role_key, $me, true ) ); ?> render_switch( 'ccamc_iface[' . $role_key . '][hide_plugin_notices]', ! empty( $cfg['hide_plugin_notices'] ), '', array( 'sr' => sprintf( /* translators: %s: role name. */ __( 'Hide plugin pop-ups for %s', 'cc-admin-menu-control' ), $name ) ) ); ?> render_switch( 'ccamc_iface[' . $role_key . '][hide_wp_info]', ! empty( $cfg['hide_wp_info'] ), '', array( 'sr' => sprintf( /* translators: %s: role name. */ __( 'Hide WordPress info for %s', 'cc-admin-menu-control' ), $name ) ) ); ?>
save_bar(); ?>
·
get_roles( true ); $counts = $this->role_user_counts(); $general = $this->get_general(); $has_wc = function_exists( 'wc_get_page_permalink' ); $structure = $this->get_menu_structure(); $this->form_open( 'save_access', array( 'data-ccamc-dirty' => '1' ) ); ?>

$role_data ) : $name = $this->role_name( $role_key, $role_data ); $cfg = $this->role_cfg( $role_key ); $n = isset( $counts[ $role_key ] ) ? (int) $counts[ $role_key ] : 0; $mode = ! empty( $cfg['block_admin'] ) ? 'blocked' : ( ! empty( $cfg['hide_dashboard'] ) ? 'no_dashboard' : 'full' ); ?>
role_cell( $role_key, $name, $n ); ?> render_switch( 'ccamc_access[' . $role_key . '][hide_admin_bar]', ! empty( $cfg['hide_admin_bar'] ), '', array( 'sr' => sprintf( /* translators: %s: role name. */ __( 'Hide the admin bar for %s', 'cc-admin-menu-control' ), $name ) ) ); ?>
—
—

>

save_bar(); ?> render_blocked_users(); ?> self::META_BLOCK, // phpcs:ignore WordPress.DB.SlowDBQuery 'meta_value' => '1', // phpcs:ignore WordPress.DB.SlowDBQuery 'number' => 300, 'orderby' => 'display_name', ) ); $blocked_ids = wp_list_pluck( $blocked, 'ID' ); $candidates = get_users( array( 'role__not_in' => array( 'administrator' ), 'exclude' => $blocked_ids, 'number' => 500, 'orderby' => 'display_name', ) ); ?>

form_open( 'block_user', array( 'class' => 'ccamc-inline-form' ) ); ?>

ID, 36, '', '', array( 'class' => 'ccamc-user-avatar' ) ) ); ?>
display_name ); ?>
user_login . ' · ' . $u->user_email ); ?>
roles ) ) ); ?> form_open( 'unblock_user' ); ?>
get_roles( true ); $counts = $this->role_user_counts(); $default_role = get_option( 'default_role' ); $me = $this->user_roles(); ?>

form_open( 'create_role' ); ?>

$role_data ) : $name = $this->role_name( $role_key, $role_data ); $n = isset( $counts[ $role_key ] ) ? (int) $counts[ $role_key ] : 0; $caps = isset( $role_data['capabilities'] ) ? count( array_filter( (array) $role_data['capabilities'] ) ) : 0; $is_mine = in_array( $role_key, $me, true ); $tags = array(); $reason = ''; if ( 'administrator' === $role_key ) { $tags[] = array( __( 'Protected', 'cc-admin-menu-control' ), 'ccamc-tag--grey' ); $reason = __( 'The Administrator role cannot be deleted.', 'cc-admin-menu-control' ); } elseif ( $role_key === $default_role ) { $tags[] = array( __( 'Default role', 'cc-admin-menu-control' ), 'ccamc-tag--green' ); $reason = __( 'Role assigned by default to new accounts (Settings › General): change it first.', 'cc-admin-menu-control' ); } elseif ( $is_mine ) { $reason = __( 'You cannot delete your own role.', 'cc-admin-menu-control' ); } $row_id = 'ccamc-del-' . $role_key; ?>
role_cell( $role_key, $name, $n, $is_mine, $tags ); ?>
verify(); check_admin_referer( 'ccamc_save_menu', 'ccamc_nonce' ); $raw = isset( $_POST['ccamc_settings'] ) && is_array( $_POST['ccamc_settings'] ) ? wp_unslash( $_POST['ccamc_settings'] ) : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput $restrict = isset( $_POST['ccamc_restrict'] ) && is_array( $_POST['ccamc_restrict'] ) ? wp_unslash( $_POST['ccamc_restrict'] ) : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput $grant = isset( $_POST['ccamc_grant'] ) && is_array( $_POST['ccamc_grant'] ) ? wp_unslash( $_POST['ccamc_grant'] ) : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput $grant_s = isset( $_POST['ccamc_grant_sensitive'] ) && is_array( $_POST['ccamc_grant_sensitive'] ) ? wp_unslash( $_POST['ccamc_grant_sensitive'] ) : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput $settings = get_option( self::OPT_MENU, array() ); $settings = is_array( $settings ) ? $settings : array(); $config = $this->get_roles_config(); foreach ( $this->get_roles( false ) as $role_key => $role_data ) { $slugs = array(); if ( isset( $raw[ $role_key ] ) && is_array( $raw[ $role_key ] ) ) { $slugs = array_values( array_unique( array_filter( array_map( array( $this, 'clean_slug' ), $raw[ $role_key ] ) ) ) ); } $settings[ $role_key ] = $slugs; $config[ $role_key ] = isset( $config[ $role_key ] ) ? (array) $config[ $role_key ] : array(); $config[ $role_key ]['restrict_menu'] = ! empty( $restrict[ $role_key ] ); $config[ $role_key ]['grant_caps'] = ! empty( $grant[ $role_key ] ); $config[ $role_key ]['grant_sensitive'] = ! empty( $grant_s[ $role_key ] ); if ( $config[ $role_key ]['grant_caps'] ) { $this->sync_role_caps( $role_key, $slugs, $config[ $role_key ]['grant_sensitive'] ); } } update_option( self::OPT_MENU, $settings ); $this->save_roles_config( $config ); $general = $this->get_general(); $general['block_direct_access'] = empty( $_POST['ccamc_block_direct'] ) ? 0 : 1; update_option( self::OPT_GENERAL, $general ); $this->back( self::PAGE, array( 'ccamc_msg' => 'saved' ) ); } public function handle_save_interface() { $this->verify(); check_admin_referer( 'ccamc_save_interface', 'ccamc_nonce' ); $raw = isset( $_POST['ccamc_iface'] ) && is_array( $_POST['ccamc_iface'] ) ? wp_unslash( $_POST['ccamc_iface'] ) : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput $config = $this->get_roles_config(); foreach ( $this->get_roles( true ) as $role_key => $role_data ) { $config[ $role_key ] = isset( $config[ $role_key ] ) ? (array) $config[ $role_key ] : array(); foreach ( array( 'hide_plugin_notices', 'hide_wp_info' ) as $flag ) { $config[ $role_key ][ $flag ] = ! empty( $raw[ $role_key ][ $flag ] ); } } $this->save_roles_config( $config ); $this->back( self::PAGE_INTERFACE, array( 'ccamc_msg' => 'saved' ) ); } public function handle_save_access() { $this->verify(); check_admin_referer( 'ccamc_save_access', 'ccamc_nonce' ); $raw = isset( $_POST['ccamc_access'] ) && is_array( $_POST['ccamc_access'] ) ? wp_unslash( $_POST['ccamc_access'] ) : array(); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput $config = $this->get_roles_config(); foreach ( $this->get_roles( false ) as $role_key => $role_data ) { $config[ $role_key ] = isset( $config[ $role_key ] ) ? (array) $config[ $role_key ] : array(); $mode = isset( $raw[ $role_key ]['mode'] ) ? sanitize_key( $raw[ $role_key ]['mode'] ) : 'full'; $config[ $role_key ]['block_admin'] = ( 'blocked' === $mode ); $config[ $role_key ]['hide_dashboard'] = ( 'no_dashboard' === $mode ); $config[ $role_key ]['landing'] = isset( $raw[ $role_key ]['landing'] ) ? $this->clean_slug( $raw[ $role_key ]['landing'] ) : ''; $config[ $role_key ]['hide_admin_bar'] = ! empty( $raw[ $role_key ]['hide_admin_bar'] ); } $this->save_roles_config( $config ); $general = $this->get_general(); $redirect = isset( $_POST['ccamc_redirect'] ) ? sanitize_key( wp_unslash( $_POST['ccamc_redirect'] ) ) : 'home'; $general['redirect'] = in_array( $redirect, array( 'home', 'account', 'custom' ), true ) ? $redirect : 'home'; $general['redirect_url'] = isset( $_POST['ccamc_redirect_url'] ) ? esc_url_raw( wp_unslash( $_POST['ccamc_redirect_url'] ) ) : ''; $msg = 'saved'; if ( 'custom' === $general['redirect'] && $general['redirect_url'] && $this->is_admin_url( $general['redirect_url'] ) ) { $general['redirect'] = 'home'; $general['redirect_url'] = ''; $msg = 'redirect_admin'; } update_option( self::OPT_GENERAL, $general ); $this->back( self::PAGE_ACCESS, array( 'ccamc_msg' => $msg ) ); } public function handle_block_user() { $this->verify(); check_admin_referer( 'ccamc_block_user', 'ccamc_nonce' ); $user = get_userdata( isset( $_POST['user_id'] ) ? absint( $_POST['user_id'] ) : 0 ); if ( ! $user || $this->is_admin_user( $user ) ) { $this->back( self::PAGE_ACCESS, array( 'ccamc_msg' => 'user_invalid' ) ); } update_user_meta( $user->ID, self::META_BLOCK, 1 ); $this->back( self::PAGE_ACCESS, array( 'ccamc_msg' => 'user_blocked', 'ccamc_name' => rawurlencode( $user->display_name ) ) ); } public function handle_unblock_user() { $this->verify(); check_admin_referer( 'ccamc_unblock_user', 'ccamc_nonce' ); $user = get_userdata( isset( $_POST['user_id'] ) ? absint( $_POST['user_id'] ) : 0 ); if ( $user ) { delete_user_meta( $user->ID, self::META_BLOCK ); $this->back( self::PAGE_ACCESS, array( 'ccamc_msg' => 'user_unblocked', 'ccamc_name' => rawurlencode( $user->display_name ) ) ); } $this->back( self::PAGE_ACCESS ); } public function handle_create_role() { $this->verify(); check_admin_referer( 'ccamc_create_role', 'ccamc_nonce' ); $name = isset( $_POST['role_name'] ) ? sanitize_text_field( wp_unslash( $_POST['role_name'] ) ) : ''; $key = isset( $_POST['role_key'] ) ? sanitize_key( wp_unslash( $_POST['role_key'] ) ) : ''; $base = isset( $_POST['role_base'] ) ? sanitize_key( wp_unslash( $_POST['role_base'] ) ) : ''; if ( '' === $key && '' !== $name ) { $key = sanitize_key( preg_replace( '/[\s\-]+/', '_', remove_accents( $name ) ) ); } if ( '' === $name || '' === $key ) { $this->back( self::PAGE_ROLES, array( 'ccamc_msg' => 'role_invalid' ) ); } if ( get_role( $key ) ) { $this->back( self::PAGE_ROLES, array( 'ccamc_msg' => 'role_exists' ) ); } $caps = array( 'read' => true ); $source = $base ? get_role( $base ) : null; if ( $source && ! empty( $source->capabilities ) ) { $caps = $source->capabilities; } add_role( $key, $name, $caps ); // Role created from scratch: it will receive the capabilities of the checked menus. if ( ! $source ) { $config = $this->get_roles_config(); $config[ $key ] = array( 'grant_caps' => true ); $this->save_roles_config( $config ); } // Copy the plugin configuration. if ( $source && ! empty( $_POST['copy_config'] ) ) { $config = $this->get_roles_config(); if ( 'administrator' === $base ) { $from = $this->role_cfg( $base ); $config[ $key ] = array( 'hide_plugin_notices' => $from['hide_plugin_notices'], 'hide_wp_info' => $from['hide_wp_info'], ); } else { $config[ $key ] = $this->role_cfg( $base ); $settings = get_option( self::OPT_MENU, array() ); if ( is_array( $settings ) && isset( $settings[ $base ] ) ) { $settings[ $key ] = $settings[ $base ]; update_option( self::OPT_MENU, $settings ); } } $this->save_roles_config( $config ); } $this->back( self::PAGE_ROLES, array( 'ccamc_msg' => 'role_created', 'ccamc_name' => rawurlencode( $name ) ) ); } public function handle_delete_role() { $this->verify(); check_admin_referer( 'ccamc_delete_role', 'ccamc_nonce' ); $role = isset( $_POST['role'] ) ? sanitize_key( wp_unslash( $_POST['role'] ) ) : ''; $reassign = isset( $_POST['reassign'] ) ? sanitize_key( wp_unslash( $_POST['reassign'] ) ) : ''; $object = $role ? get_role( $role ) : null; if ( ! $object || 'administrator' === $role || get_option( 'default_role' ) === $role || in_array( $role, $this->user_roles(), true ) ) { $this->back( self::PAGE_ROLES, array( 'ccamc_msg' => 'role_protected' ) ); } $name = $this->role_name( $role ); if ( ! $reassign || $reassign === $role || ! get_role( $reassign ) ) { $reassign = get_option( 'default_role', 'subscriber' ); if ( $reassign === $role || ! get_role( $reassign ) ) { $reassign = 'subscriber'; } } $moved = 0; $users = get_users( array( 'role' => $role ) ); foreach ( $users as $user ) { $user->remove_role( $role ); if ( empty( $user->roles ) && get_role( $reassign ) ) { $user->add_role( $reassign ); } ++$moved; } remove_role( $role ); // Clean up the configuration. $settings = get_option( self::OPT_MENU, array() ); if ( is_array( $settings ) && isset( $settings[ $role ] ) ) { unset( $settings[ $role ] ); update_option( self::OPT_MENU, $settings ); } $config = $this->get_roles_config(); if ( isset( $config[ $role ] ) ) { unset( $config[ $role ] ); $this->save_roles_config( $config ); } $granted = $this->get_granted(); if ( isset( $granted[ $role ] ) ) { unset( $granted[ $role ] ); update_option( self::OPT_GRANTED, $granted, false ); } $this->back( self::PAGE_ROLES, array( 'ccamc_msg' => 'role_deleted', 'ccamc_name' => rawurlencode( $name ), 'ccamc_n' => $moved, ) ); } } CCAMC_Plugin::get_instance();