main_file = $file; require_once plugin_dir_path( __FILE__ ) . 'customizer.php'; // Admin functions. add_action( 'admin_init', array( $this, 'admin_init' ) ); // Load the textdomain. add_action( 'init', array( $this, 'load_text_domain' ) ); // Register the meta key. add_action( 'init', array( $this, 'register_meta' ) ); // Add FAQ and Donate link to plugin. add_filter( 'plugin_row_meta', array( $this, 'add_action_links' ), 10, 2 ); // Maybe switch the admin walker. if ( ! self::is_wp_gte( '5.4' ) ) { add_filter( 'wp_edit_nav_menu_walker', array( $this, 'edit_nav_menu_walker' ) ); } // Add new fields via hook. add_action( 'wp_nav_menu_item_custom_fields', array( $this, 'custom_fields' ), 10, 4 ); // Add some JS. add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); // Save the menu item meta. add_action( 'wp_update_nav_menu_item', array( $this, 'nav_update' ), 10, 2 ); // Add meta to menu item. add_filter( 'wp_setup_nav_menu_item', array( $this, 'setup_nav_item' ) ); // Exclude items via filter instead of via custom Walker. if ( ! is_admin() ) { // Because WP_Customize_Nav_Menu_Item_Setting::filter_wp_get_nav_menu_items() runs at 10. add_filter( 'wp_get_nav_menu_items', array( $this, 'exclude_menu_items' ), 20 ); } // Upgrade routine. add_action( 'plugins_loaded', array( $this, 'maybe_upgrade' ) ); } /** * Include the custom admin walker * * @access public * @return void */ public function admin_init() { // Register Importer. $this->register_importer(); } /** * Register the Importer * the regular Importer skips post meta for the menu items * * @access private * @return void */ public function register_importer() { // Register the new importer. if ( defined( 'WP_LOAD_IMPORTERS' ) ) { include_once plugin_dir_path( __FILE__ ) . 'class-nav-menu-roles-import.php'; // Register the custom importer we've created. $roles_import = new Nav_Menu_Roles_Import(); register_importer( 'nav_menu_roles', __( 'Nav Menu Roles', 'nav-menu-roles' ), __( 'Import nav menu roles and other menu item meta skipped by the default importer', 'nav-menu-roles' ), array( $roles_import, 'dispatch' ) ); } } /** * Make Plugin Translation-ready * * @since 1.0 */ public function load_text_domain() { load_plugin_textdomain( 'nav-menu-roles', false, dirname( plugin_basename( $this->main_file ) ) . '/languages/' ); } /** * Register the meta keys for nav menus. * * @since 2.0 */ public function register_meta() { register_meta( 'post', '_nav_menu_role', array( 'object_subtype' => 'nav_menu_item', 'type' => 'mixed', 'sanitize_callback' => array( $this, 'sanitize_meta' ), ) ); register_meta( 'post', '_nav_menu_role_display_mode', array( 'object_subtype' => 'nav_menu_item', 'type' => 'mixed', 'sanitize_callback' => array( $this, 'sanitize_meta_mode' ), ) ); } /** * Sanitize the meta. * * @since 2.0.0 * * @param mixed $meta_value The meta value. * @return mixed The meta value. * */ public function sanitize_meta( $meta_value ) { global $wp_roles; $clean = ''; if ( is_array( $meta_value ) ) { $clean = array(); /** * Pass the menu item to the filter function. * This change is suggested as it allows the use of information from the menu item (and * by extension the target object) to further customize what filters appear during menu * construction. */ $allowed_roles = apply_filters( 'nav_menu_roles', $wp_roles->role_names ); // Only save allowed roles. $clean = array_intersect( $meta_value, array_keys( $allowed_roles ) ); } elseif ( in_array( $meta_value, array( 'in', 'out' ) ) ) { $clean = $meta_value; } return $clean; } /** * Sanitize the display mode meta. * * @since 2.1.0 * * @param mixed $meta_value The meta value. * @return mixed The meta value. * */ public function sanitize_meta_mode( $meta_value ) { return 'hide' === $meta_value ? 'hide' : 'show'; } /** * Display a Notice if plugin conflicts with another * * @since 1.5 * @deprecated will removed in 2.0 */ public function admin_notice() { _deprecated_function( __METHOD__, '1.7.8' ); } /** * Allow the notice to be dismissable * * @since 1.6 * @deprecated will removed in 2.0 */ public function nag_ignore() { _deprecated_function( __METHOD__, '1.7.8' ); } /** * Delete the transient when a plugin is activated or deactivated * * @since 1.5 * @deprecated will removed in 2.0 */ public function delete_transient() { _deprecated_function( __METHOD__, '1.7.8' ); delete_transient( 'nav_menu_roles_conflicts' ); } /** * Add docu link * * @since 1.7.3 * @param array $plugin_meta * @param string $plugin_file */ public function add_action_links( $plugin_meta, $plugin_file ) { if ( plugin_basename( $this->main_file ) === $plugin_file ) { $plugin_meta[] = sprintf( '%s', __( 'FAQ', 'nav-menu-roles' ) ); $plugin_meta[] = '' . __( 'Donate', 'nav-menu-roles' ) . ''; } return $plugin_meta; } /** * Override the Admin Menu Walker * * @since 1.0 */ public function edit_nav_menu_walker( $walker ) { if ( ! class_exists( 'Walker_Nav_Menu_Edit_Roles' ) ) { if ( self::is_wp_gte( '4.7' ) ) { require_once plugin_dir_path( __FILE__ ) . 'class-walker-nav-menu-edit-roles-4.7.php'; } elseif ( self::is_wp_gte( '4.5' ) ) { require_once plugin_dir_path( __FILE__ ) . 'class-walker-nav-menu-edit-roles-4.5.php'; } else { require_once plugin_dir_path( __FILE__ ) . 'class-walker-nav-menu-edit-roles.php'; } } return 'Walker_Nav_Menu_Edit_Roles'; } /** * Add fields to hook added in Walker * This will allow us to play nicely with any other plugin that is adding the same hook * @params obj $item - the menu item * @params array $args * @since 1.6.0 */ public function custom_fields( $item_id, $item, $depth, $args ) { global $wp_roles; /** * Pass the menu item to the filter function. * This change is suggested as it allows the use of information from the menu item (and * by extension the target object) to further customize what filters appear during menu * construction. */ $display_roles = apply_filters( 'nav_menu_roles', $wp_roles->role_names, $item ); // Alpha sort roles by label. asort( $wp_roles->role_names ); /** * If no roles are being used, don't display the role selection radio buttons at all. * Unless something deliberately removes the WordPress roles from this list, nothing will * be functionally altered to the end user. * This change is suggested for the benefit of users constructing granular admin permissions * using extensive custom roles as it is an effective means of stopping admins with partial * permissions to the menu from accidentally removing all restrictions from a menu item to * which they do not have access. */ if ( ! $display_roles ) { return; } /* Get the roles saved for the post. */ $roles = get_post_meta( $item->ID, '_nav_menu_role', true ); // By default nothing is checked (will match "everyone" radio). $logged_in_out = ''; // Show/Hide items to specific users. $display_mode = 'hide' === get_post_meta( $item->ID, '_nav_menu_role_display_mode', true ) ? 'hide' : 'show'; // Specific roles are saved as an array, so "in" or an array equals "in" is checked. if ( is_array( $roles ) || 'in' === $roles ) { $logged_in_out = 'in'; } elseif ( 'out' === $roles ) { $logged_in_out = 'out'; } // The specific roles to check. $checked_roles = is_array( $roles ) ? $roles : false; // Whether to display the role checkboxes. $hidden = 'in' === $logged_in_out ? '' : 'display: none;'; $float = is_rtl() ? 'float:"right";' : 'float:"left";'; ?> main_file ), array( 'jquery' ), self::VERSION, true ); } } /** * Save the roles as menu item meta * * @since 1.0 * @return string */ public function nav_update( $menu_id, $menu_item_db_id ) { // Verify this came from our screen and with proper authorization. if ( ! isset( $_POST['nav-menu-role-nonce'] ) || ! wp_verify_nonce( wp_unslash( $_POST['nav-menu-role-nonce'] ), 'nav-menu-nonce-name' ) ) { return $menu_id; } // Save display mode. if ( isset( $_POST['nav-menu-display-mode'][ $menu_item_db_id ] ) && 'hide' === wp_unslash( $_POST['nav-menu-display-mode'][ $menu_item_db_id ] ) ) { update_post_meta( $menu_item_db_id, '_nav_menu_role_display_mode', 'hide' ); } else { update_post_meta( $menu_item_db_id, '_nav_menu_role_display_mode', 'show' ); } // Save target/roles. if ( isset( $_POST['nav-menu-logged-in-out'][ $menu_item_db_id ] ) ) { if ( 'in' === $_POST['nav-menu-logged-in-out'][ $menu_item_db_id ] && ! empty( $_POST['nav-menu-role'][ $menu_item_db_id ] ) ) { $meta = wp_unslash( $_POST['nav-menu-role'][ $menu_item_db_id ] ); } else { $meta = wp_unslash( $_POST['nav-menu-logged-in-out'][ $menu_item_db_id ] ); } update_post_meta( $menu_item_db_id, '_nav_menu_role', $meta ); // Sanitization handled by $this->sanitize_meta(). } else { delete_post_meta( $menu_item_db_id, '_nav_menu_role' ); } return $menu_id; } /** * Adds value of new field to $item object * is be passed to Walker_Nav_Menu_Edit_Custom * * @since 1.0 */ public function setup_nav_item( $menu_item ) { if ( is_object( $menu_item ) && isset( $menu_item->ID ) ) { $menu_item->display_mode = 'hide' === get_post_meta( $menu_item->ID, '_nav_menu_role_display_mode', true ) ? 'hide' : 'show'; $roles = get_post_meta( $menu_item->ID, '_nav_menu_role', true ); if ( ! empty( $roles ) ) { $menu_item->roles = $roles; // Add the NMR roles as CSS info. $new_classes = array(); switch ( $roles ) { case 'in': $new_classes[] = 'nmr-logged-in'; break; case 'out': $new_classes[] = 'nmr-logged-out'; break; default: if ( is_array( $menu_item->roles ) && ! empty( $menu_item->roles ) ) { foreach ( $menu_item->roles as $role ) { $new_classes[] = 'nmr-' . $role; } } break; } // Only apply classes on front-end. if ( ! is_admin() ) { $menu_item->classes = array_unique( array_merge( (array) $menu_item->classes, (array) $new_classes ) ); } } } return $menu_item; } /** * Exclude menu items via wp_get_nav_menu_items filter * this fixes plugin's incompatibility with theme's that use their own custom Walker * Thanks to Evan Stein @vanpop http://vanpop.com/ * * @since 1.2 * * @param WP_Post[] array of Nav Menu Post objects * * Multisite compatibility added in 1.9.0 * by @open-dsi https://www.open-dsi.fr/ with props to @fiech */ public function exclude_menu_items( $items ) { $hide_children_of = array(); if ( ! empty( $items ) ) { // Iterate over the items to search and destroy. foreach ( $items as $key => $item ) { $visible = true; $is_hidden_child = false; // Hide any item that is the child of a hidden item. if ( ! empty( $item->menu_item_parent ) && in_array( $item->menu_item_parent, $hide_children_of ) ) { $visible = false; $is_hidden_child = true; } // Check any item that has NMR roles set. if ( $visible && isset( $item->roles ) ) { // Check all logged in, all logged out, or role. switch ( $item->roles ) { case 'in': /** * Multisite compatibility. * * For the logged in condition to work, * the user has to be a logged in member of the current blog * or be a logged in super user. */ $visible = is_user_member_of_blog() || is_super_admin() ? true : false; break; case 'out': /** * Multisite compatibility. * * For the logged out condition to work, * the user has to be either logged out * or not be a member of the current blog. * But they also may not be a super admin, * because logged in super admins should see the internal stuff, not the external. */ $visible = ! is_user_member_of_blog() && ! is_super_admin() ? true : false; break; default: $visible = false; if ( is_array( $item->roles ) && ! empty( $item->roles ) ) { foreach ( $item->roles as $role ) { if ( current_user_can( $role ) ) { $visible = true; break; } } } break; } } // Invert visibility if display mode is "hide" for items not already hidden as children of hidden parent. if ( ! $is_hidden_child && ! empty( $item->display_mode ) && 'hide' === $item->display_mode ) { $visible = ! $visible; } /* * Filter: nav_menu_roles_item_visibility * Add filter to work with plugins that don't use traditional roles * * @param bool $visible * @param object $item */ $visible = apply_filters( 'nav_menu_roles_item_visibility', $visible, $item ); // Unset non-visible item. if ( ! $visible ) { if ( isset( $item->ID ) ) { $hide_children_of[] = $item->ID; // Store ID of item to hide it's children. } unset( $items[ $key ] ); } } } return $items; } /** * Maybe upgrade * * @access public * @return void */ public function maybe_upgrade() { $db_version = get_option( 'nav_menu_roles_db_version', false ); // 1.7.7 upgrade: changed the debug notice so the old transient is invalid. if ( false === $db_version || version_compare( '1.7.7', $db_version, '<' ) ) { update_option( 'nav_menu_roles_db_version', self::VERSION ); } } /** * Test WordPress version * * @access public * @param string $version - A WordPress version to compare against current version. * @return boolean */ public static function is_wp_gte( $version = '5.4' ) { global $wp_version; return version_compare( strtolower( $wp_version ), strtolower( $version ), '>=' ); } } // End class.