1, 'active_preset' => 'brochure', 'target_roles' => array( 'editor', 'author', 'shop_manager' ), 'hide_notices' => 1, 'hidden_menus' => array( 'edit-comments.php', 'tools.php', 'themes.php', 'plugins.php', 'options-general.php', ), 'essential_blocks_only' => 1, 'lock_inspector_styles' => 1, 'disable_code_editor' => 1, ); } /** * Get merged plugin settings. * * @return array */ function chmde_get_settings() { $saved = get_option( CHMDE_OPTION_KEY, array() ); if ( ! is_array( $saved ) ) { $saved = array(); } return wp_parse_args( $saved, chmde_get_default_settings() ); } /** * Determine whether the current user should experience the simplified Client Handover view. * * @return bool */ function chmde_is_client_view_active() { $settings = chmde_get_settings(); if ( empty( $settings['enabled'] ) ) { return false; } $user = wp_get_current_user(); if ( ! $user || ! $user->exists() ) { return false; } // If Administrator, only apply if they explicitly turned on "Preview as Client". if ( current_user_can( 'manage_options' ) ) { $preview_flag = get_user_meta( $user->ID, 'chmde_preview_as_client', true ); return ( '1' === (string) $preview_flag ); } $target_roles = isset( $settings['target_roles'] ) && is_array( $settings['target_roles'] ) ? $settings['target_roles'] : array(); foreach ( (array) $user->roles as $role ) { if ( in_array( $role, $target_roles, true ) ) { return true; } } return false; } /** * Register Settings and Strict Sanitization Callback. */ function chmde_register_settings() { register_setting( 'chmde_settings_group', CHMDE_OPTION_KEY, array( 'type' => 'array', 'sanitize_callback' => 'chmde_sanitize_settings', 'default' => chmde_get_default_settings(), ) ); } add_action( 'admin_init', 'chmde_register_settings' ); /** * Sanitize all incoming settings strictly before saving to the database. * * @param mixed $input Raw input from form submission. * @return array Sanitized settings array. */ function chmde_sanitize_settings( $input ) { if ( ! current_user_can( 'manage_options' ) || ! is_array( $input ) ) { return chmde_get_settings(); } $clean = array(); $clean['enabled'] = ! empty( $input['enabled'] ) ? 1 : 0; $allowed_presets = array( 'brochure', 'blog', 'shop', 'custom' ); $raw_preset = isset( $input['active_preset'] ) ? sanitize_key( $input['active_preset'] ) : 'custom'; $clean['active_preset'] = in_array( $raw_preset, $allowed_presets, true ) ? $raw_preset : 'custom'; $clean['hide_notices'] = ! empty( $input['hide_notices'] ) ? 1 : 0; $clean['essential_blocks_only'] = ! empty( $input['essential_blocks_only'] ) ? 1 : 0; $clean['lock_inspector_styles'] = ! empty( $input['lock_inspector_styles'] ) ? 1 : 0; $clean['disable_code_editor'] = ! empty( $input['disable_code_editor'] ) ? 1 : 0; // Sanitize target roles against registered WP roles. $clean['target_roles'] = array(); if ( ! empty( $input['target_roles'] ) && is_array( $input['target_roles'] ) ) { $wp_roles = wp_roles()->get_names(); foreach ( $input['target_roles'] as $role_key ) { $sanitized_role = sanitize_key( $role_key ); if ( isset( $wp_roles[ $sanitized_role ] ) && 'administrator' !== $sanitized_role ) { $clean['target_roles'][] = $sanitized_role; } } } // Sanitize hidden menu slugs. $clean['hidden_menus'] = array(); if ( isset( $input['hidden_menus'] ) && is_array( $input['hidden_menus'] ) ) { foreach ( $input['hidden_menus'] as $menu_slug ) { if ( ! is_scalar( $menu_slug ) ) { continue; } $slug = trim( sanitize_text_field( wp_unslash( (string) $menu_slug ) ) ); if ( '' !== $slug ) { $clean['hidden_menus'][] = $slug; } } } $clean['hidden_menus'] = array_values( array_unique( $clean['hidden_menus'] ) ); // Set transient for clear UX feedback after save. set_transient( 'chmde_saved_feedback_' . get_current_user_id(), 'settings_saved', 30 ); return $clean; } /** * Handle "Preview as Client" toggle in the Admin Bar with Nonce & Capability verification. */ function chmde_handle_preview_toggle() { if ( ! isset( $_GET['chmde_toggle_preview'] ) ) { return; } if ( ! current_user_can( 'manage_options' ) ) { wp_die( esc_html__( 'You do not have permission to perform this action.', 'client-handover-mode' ) ); } check_admin_referer( 'chmde_toggle_preview_action', 'chmde_nonce' ); $user_id = get_current_user_id(); $current_flag = get_user_meta( $user_id, 'chmde_preview_as_client', true ); $new_flag = ( '1' === (string) $current_flag ) ? '0' : '1'; update_user_meta( $user_id, 'chmde_preview_as_client', $new_flag ); set_transient( 'chmde_saved_feedback_' . $user_id, ( '1' === $new_flag ) ? 'preview_enabled' : 'preview_disabled', 30 ); $redirect_url = remove_query_arg( array( 'chmde_toggle_preview', 'chmde_nonce' ) ); wp_safe_redirect( $redirect_url ); exit; } add_action( 'admin_init', 'chmde_handle_preview_toggle' ); /** * Add Admin Bar "Preview as Client" Switch with Live Status Indicator. * * @param WP_Admin_Bar $wp_admin_bar WP_Admin_Bar instance. */ function chmde_admin_bar_preview_switch( $wp_admin_bar ) { if ( ! current_user_can( 'manage_options' ) || ! is_admin() ) { return; } $is_preview = ( '1' === (string) get_user_meta( get_current_user_id(), 'chmde_preview_as_client', true ) ); $toggle_url = wp_nonce_url( add_query_arg( 'chmde_toggle_preview', '1' ), 'chmde_toggle_preview_action', 'chmde_nonce' ); $title = $is_preview ? '🟢 ' . esc_html__( 'Client Preview: ON (Click to Exit)', 'client-handover-mode' ) : '👁️ ' . esc_html__( 'Preview as Client', 'client-handover-mode' ); $wp_admin_bar->add_node( array( 'id' => 'chmde-preview-toggle', 'title' => $title, 'href' => esc_url( $toggle_url ), 'meta' => array( 'class' => $is_preview ? 'chmde-adminbar-active' : 'chmde-adminbar-inactive', 'title' => esc_attr__( 'Toggle Client Handover Mode Preview', 'client-handover-mode' ), ), ) ); } add_action( 'admin_bar_menu', 'chmde_admin_bar_preview_switch', 100 ); /** * Add Settings Menu Page. */ function chmde_register_menu_page() { add_options_page( esc_html__( 'Client Handover Mode', 'client-handover-mode' ), esc_html__( 'Handover Mode', 'client-handover-mode' ), 'manage_options', 'client-handover-mode', 'chmde_render_settings_page' ); } add_action( 'admin_menu', 'chmde_register_menu_page' ); /** * Apply Admin Sidebar Declutter for Client View. */ function chmde_apply_menu_declutter() { if ( ! chmde_is_client_view_active() ) { return; } $settings = chmde_get_settings(); $hidden_menus = ( isset( $settings['hidden_menus'] ) && is_array( $settings['hidden_menus'] ) ) ? array_values( array_unique( array_filter( array_map( 'sanitize_text_field', $settings['hidden_menus'] ) ) ) ) : array(); // Re-run late so menus registered by other plugins are present before removal. foreach ( $hidden_menus as $slug ) { $slug = trim( (string) $slug ); if ( '' === $slug ) { continue; } // Administrators can preview the client view without losing access to settings. if ( current_user_can( 'manage_options' ) && in_array( $slug, array( 'options-general.php', 'settings' ), true ) ) { continue; } // Hide matching top-level menu entries. remove_menu_page( $slug ); // Also remove a matching submenu entry if the selected slug belongs to a submenu. global $submenu; if ( is_array( $submenu ) ) { foreach ( $submenu as $parent_slug => $items ) { if ( ! is_array( $items ) ) { continue; } foreach ( $items as $item ) { if ( isset( $item[2] ) && (string) $item[2] === $slug ) { remove_submenu_page( $parent_slug, $slug ); } } } } } } add_action( 'admin_menu', 'chmde_apply_menu_declutter', PHP_INT_MAX ); /** * Filter Gutenberg Block Inserter to show only essential content blocks for clients. * * @param bool|string[] $allowed_block_types Array of block type slugs, or boolean. * @param WP_Block_Editor_Context $editor_context The current block editor context. * @return bool|string[] */ function chmde_filter_allowed_blocks( $allowed_block_types, $editor_context ) { if ( ! chmde_is_client_view_active() ) { return $allowed_block_types; } $settings = chmde_get_settings(); if ( empty( $settings['essential_blocks_only'] ) ) { return $allowed_block_types; } return array( 'core/paragraph', 'core/heading', 'core/list', 'core/list-item', 'core/image', 'core/gallery', 'core/buttons', 'core/button', 'core/quote', 'core/group', 'core/columns', 'core/column', 'core/cover', 'core/spacer', ); } add_filter( 'allowed_block_types_all', 'chmde_filter_allowed_blocks', 10, 2 ); /** * Lock complex Inspector controls (spacing, typography, responsive states, code editor) for clients. * * @param array $editor_settings Existing editor settings. * @param WP_Block_Editor_Context $editor_context Current block editor context. * @return array */ function chmde_filter_block_editor_settings( $editor_settings, $editor_context ) { if ( ! chmde_is_client_view_active() ) { return $editor_settings; } $settings = chmde_get_settings(); if ( ! empty( $settings['disable_code_editor'] ) ) { $editor_settings['codeEditingEnabled'] = false; } if ( ! empty( $settings['lock_inspector_styles'] ) ) { $editor_settings['responsiveEditingEnabled'] = false; $editor_settings['blockStatesEditingEnabled'] = false; $editor_settings['canLockBlocks'] = false; } return $editor_settings; } add_filter( 'block_editor_settings_all', 'chmde_filter_block_editor_settings', 10, 2 ); /** * Prevent Muted Sections (`chmdeMuted` attribute) from outputting HTML on the live frontend. * * @param string $block_content The block content. * @param array $block The full block, including name and attributes. * @return string */ function chmde_mute_hidden_sections_frontend( $block_content, $block ) { if ( is_admin() ) { return $block_content; } if ( ! empty( $block['attrs']['chmdeMuted'] ) ) { return ''; } return $block_content; } add_filter( 'render_block', 'chmde_mute_hidden_sections_frontend', 10, 2 ); /** * Enqueue plugin assets only on the settings screen and supported block editor screens. * Promotional notice filtering uses its own inline stylesheet so it can work across * the admin area without loading the settings UI assets everywhere. * * @param string $hook_suffix Current admin page hook. */ function chmde_enqueue_assets( $hook_suffix ) { $settings_screen = 'settings_page_client-handover-mode' === $hook_suffix; $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null; $editor_screen = false; if ( in_array( $hook_suffix, array( 'post.php', 'post-new.php' ), true ) && $screen && ! empty( $screen->post_type ) ) { $editor_screen = post_type_supports( $screen->post_type, 'editor' ) && function_exists( 'use_block_editor_for_post_type' ) && use_block_editor_for_post_type( $screen->post_type ); } $settings = chmde_get_settings(); // Apply the optional notice filter on admin screens only for users in Client View. if ( chmde_is_client_view_active() && ! empty( $settings['hide_notices'] ) ) { wp_register_style( 'chmde-notice-filter', false, array(), CHMDE_VERSION ); wp_enqueue_style( 'chmde-notice-filter' ); $notice_filter_css = ' .notice[class*="upsell"], .notice[class*="promo"], .notice[class*="promotion"], .notice[class*="review-nag"], .notice[class*="rating-nag"], .notice[class*="rate-plugin"], .notice[class*="upgrade-nag"], .notice[class*="upgrade-prompt"] { display: none !important; }'; wp_add_inline_style( 'chmde-notice-filter', $notice_filter_css ); } if ( ! $settings_screen && ! $editor_screen ) { return; } wp_enqueue_style( 'chmde-styles', CHMDE_PLUGIN_URL . 'assets/css/chmde-styles.css', array(), CHMDE_VERSION ); $script_dependencies = $editor_screen ? array( 'wp-blocks', 'wp-element', 'wp-components', 'wp-block-editor', 'wp-compose', 'wp-hooks', 'wp-data', 'wp-i18n' ) : array(); wp_enqueue_script( 'chmde-scripts', CHMDE_PLUGIN_URL . 'assets/js/chmde-scripts.js', $script_dependencies, CHMDE_VERSION, true ); $feedback_code = get_transient( 'chmde_saved_feedback_' . get_current_user_id() ); if ( $feedback_code ) { delete_transient( 'chmde_saved_feedback_' . get_current_user_id() ); } wp_localize_script( 'chmde-scripts', 'chmdeData', array( 'isClientView' => chmde_is_client_view_active(), 'lockInspector' => ! empty( $settings['lock_inspector_styles'] ) && chmde_is_client_view_active(), 'feedbackCode' => $feedback_code ? sanitize_key( $feedback_code ) : '', 'presets' => array( 'brochure' => array( 'label' => __( 'Brochure / Service Site preset loaded — click Save Changes to apply.', 'client-handover-mode' ), 'hidden_menus' => array( 'edit.php', 'edit-comments.php', 'tools.php', 'themes.php', 'plugins.php', 'options-general.php' ), ), 'blog' => array( 'label' => __( 'Blog / Content Manager preset loaded — click Save Changes to apply.', 'client-handover-mode' ), 'hidden_menus' => array( 'edit-comments.php', 'tools.php', 'themes.php', 'plugins.php', 'options-general.php' ), ), 'shop' => array( 'label' => __( 'WooCommerce Shop Owner preset loaded — click Save Changes to apply.', 'client-handover-mode' ), 'hidden_menus' => array( 'edit.php', 'edit-comments.php', 'tools.php', 'themes.php', 'plugins.php', 'options-general.php' ), ), ), 'i18n' => array( 'muteSection' => __( 'Mute Section (Hide on Live Site)', 'client-handover-mode' ), 'unmuteSection' => __( 'Unmute Section (Publish to Live Site)', 'client-handover-mode' ), 'mutedBadge' => __( 'Muted Section (Hidden from Live Website) — Click Eye Icon in Toolbar to Restore', 'client-handover-mode' ), 'toastMuted' => __( 'Section muted — hidden from the live website.', 'client-handover-mode' ), 'toastUnmuted' => __( 'Section unmuted — now visible on the live website.', 'client-handover-mode' ), 'toastSaved' => __( 'Handover Mode settings saved and active!', 'client-handover-mode' ), 'toastPreviewOn' => __( 'Preview as Client is now ON. You are seeing the simplified workspace.', 'client-handover-mode' ), 'toastPreviewOff' => __( 'Preview as Client is OFF. Full Admin view restored.', 'client-handover-mode' ), 'unsavedNotice' => __( 'You have unsaved changes — click Save Handover Settings.', 'client-handover-mode' ), ), ) ); } add_action( 'admin_enqueue_scripts', 'chmde_enqueue_assets' ); /** * Render the Settings Page with Accessible UX Feedback, Tabs, and Live Counters. */ function chmde_render_settings_page() { if ( ! current_user_can( 'manage_options' ) ) { return; } global $menu; $settings = chmde_get_settings(); $wp_roles = wp_roles()->get_names(); $is_preview = ( '1' === (string) get_user_meta( get_current_user_id(), 'chmde_preview_as_client', true ) ); $hidden_count = isset( $settings['hidden_menus'] ) && is_array( $settings['hidden_menus'] ) ? count( $settings['hidden_menus'] ) : 0; ?>

.*?<\/span>/i', '', $item[0] ) ); if ( empty( $menu_title ) ) { continue; } $is_hidden = in_array( $menu_slug, $settings['hidden_menus'], true ); ?>