_x( 'Logo Sliders', 'post type general name', 'developersd-logo-slider' ), 'singular_name' => _x( 'Logo Slider', 'post type singular name', 'developersd-logo-slider' ), 'menu_name' => __( 'DeveloperSD Logo Slider', 'developersd-logo-slider' ), 'add_new' => __( 'Add New Slider', 'developersd-logo-slider' ), 'add_new_item' => __( 'Add New Slider', 'developersd-logo-slider' ), 'edit_item' => __( 'Edit Slider', 'developersd-logo-slider' ), 'new_item' => __( 'New Slider', 'developersd-logo-slider' ), 'view_item' => __( 'View Slider', 'developersd-logo-slider' ), 'search_items' => __( 'Search Sliders', 'developersd-logo-slider' ), 'not_found' => __( 'No sliders found. Create your first logo slider to get started.', 'developersd-logo-slider' ), 'not_found_in_trash' => __( 'No sliders found in Trash.', 'developersd-logo-slider' ), 'all_items' => __( 'All Sliders', 'developersd-logo-slider' ), ); $args = array( 'labels' => $labels, 'public' => false, 'publicly_queryable' => false, 'exclude_from_search' => true, 'show_ui' => true, 'show_in_menu' => false, // Registered manually under our own top-level menu. 'show_in_admin_bar' => false, 'show_in_rest' => false, 'query_var' => false, 'rewrite' => false, 'has_archive' => false, 'hierarchical' => false, 'supports' => array( 'title' ), // Intentionally use the default 'post' capability mapping (edit_posts, // edit_others_posts, publish_posts, delete_posts, edit_post, read_post, // delete_post, etc.) rather than a custom override. // // WARNING FOR FUTURE MAINTAINERS: do not map 'edit_post' / 'read_post' / // 'delete_post' to an existing WordPress capability name (such as // 'manage_options' or 'edit_theme_options') here. register_post_type() // feeds those three specific keys into _post_type_meta_capabilities(), // which registers whatever string you provide as a GLOBAL meta // capability name. Reusing 'manage_options' there previously caused // every current_user_can('manage_options') check site-wide — Settings, // Plugins, most of wp-admin — to be silently routed through post-meta // capability resolution with no post object, which resolved to // 'do_not_allow' for every user, including real Administrators. // Administrators (and Editors) already hold the default 'post' // capabilities below, so no custom capability grant is needed for the // slider post type itself. The 'manage_options' check is only ever // used directly (e.g. current_user_can(), or as an add_submenu_page() // argument) for this plugin's own Settings/Help screens — that usage // is completely safe; only feeding it into this specific args key is not. 'capability_type' => 'post', 'map_meta_cap' => true, ); register_post_type( self::POST_TYPE, $args ); } /** * Register post meta with sanitize callbacks so any direct meta write * (including from trusted internal code) stays consistent. */ public static function register_meta() { register_post_meta( self::POST_TYPE, self::META_LOGOS, array( 'type' => 'string', 'single' => true, 'show_in_rest' => false, 'sanitize_callback' => function ( $value ) { $decoded = is_string( $value ) ? json_decode( $value, true ) : $value; return wp_json_encode( DeveloperSD_LS_Security::sanitize_logos( $decoded ) ); }, 'auth_callback' => array( __CLASS__, 'meta_auth_callback' ), ) ); register_post_meta( self::POST_TYPE, self::META_STATUS, array( 'type' => 'string', 'single' => true, 'show_in_rest' => false, 'sanitize_callback' => function ( $value ) { return 'active' === $value ? 'active' : 'inactive'; }, 'auth_callback' => array( __CLASS__, 'meta_auth_callback' ), ) ); } /** * Auth callback for post meta edits. * * @return bool */ public static function meta_auth_callback() { return DeveloperSD_LS_Security::current_user_can_manage(); } } new DeveloperSD_LS_Post_Type();