[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(); ?>
$t ) : ?>
>
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 '
%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 ); ?>
>
>
>
$parent['slug'], 'title' => $parent['title'] ) );
foreach ( $parent['submenu'] as $sub ) {
if ( $sub['slug'] !== $parent['slug'] ) {
$options[] = $sub;
}
}
foreach ( $options as $opt ) :
$val = $this->normalize_slug( $opt['slug'] );
?>
>
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' ) ); ?>
is_admin_user( $u ) ) {
continue;
}
?>
display_name . ' (' . $u->user_login . ')' ); ?>
ID, 36, '', '', array( 'class' => 'ccamc-user-avatar' ) ) ); ?>
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 ); ?>
form_open( 'delete_role', array( 'class' => 'ccamc-delete-form', 'data-name' => $name ) ); ?>
0 ) : ?>
$d ) :
if ( $k === $role_key ) {
continue;
}
?>
>role_name( $k, $d ) ); ?>
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();