_x( 'Model Textures', 'post type general name', '3d-product-customizer' ), 'singular_name' => _x( 'Model Texture', 'post type singular name', '3d-product-customizer' ), 'menu_name' => _x( 'Model Textures', 'admin menu', '3d-product-customizer' ), 'not_found' => __( 'No model textures found.', '3d-product-customizer' ), 'not_found_in_trash' => __( 'No model textures found in Trash.', '3d-product-customizer' ), ); $args = array( 'labels' => $labels, 'public' => false, 'publicly_queryable' => false, 'show_ui' => true, 'show_in_menu' => 'dprcu-settings', 'query_var' => false, 'rewrite' => false, 'capability_type' => 'post', 'has_archive' => false, 'hierarchical' => false, 'menu_position' => 20, 'menu_icon' => 'dashicons-format-image', 'supports' => array('title', 'thumbnail'), 'show_in_rest' => true, ); register_post_type( 'dprcu_model_texture', $args ); } add_action( 'init', 'dprcu_textures_cpt_init' ); /** * Register the 'Texture Category' taxonomy for Model Textures. */ function dprcu_textures_taxonomy_init() { $labels = array( 'name' => _x( 'Texture Categories', 'taxonomy general name', '3d-product-customizer' ), 'singular_name' => _x( 'Texture Category', 'taxonomy singular name', '3d-product-customizer' ), 'search_items' => __( 'Search Texture Categories', '3d-product-customizer' ), 'all_items' => __( 'All Texture Categories', '3d-product-customizer' ), 'parent_item' => __( 'Parent Category', '3d-product-customizer' ), 'parent_item_colon' => __( 'Parent Category:', '3d-product-customizer' ), 'edit_item' => __( 'Edit Texture Category', '3d-product-customizer' ), 'update_item' => __( 'Update Texture Category', '3d-product-customizer' ), 'add_new_item' => __( 'Add New Texture Category', '3d-product-customizer' ), 'new_item_name' => __( 'New Texture Category Name', '3d-product-customizer' ), 'menu_name' => __( 'Categories', '3d-product-customizer' ), ); register_taxonomy( 'dprcu_texture_category', 'dprcu_model_texture', array( 'labels' => $labels, 'hierarchical' => true, 'show_ui' => true, 'show_in_menu' => true, 'show_in_rest' => true, 'show_admin_column' => true, 'rewrite' => false, 'query_var' => false, ) ); } add_action( 'init', 'dprcu_textures_taxonomy_init' ); /** * Add the Texture Categories submenu under the plugin's custom menu. * * WordPress only auto-adds taxonomy submenus for CPTs with their own * top-level menu entry. Since dprcu_model_texture uses show_in_menu pointing * to 'dprcu-settings', the submenu must be registered manually. */ function dprcu_textures_taxonomy_submenu() { add_submenu_page( 'dprcu-settings', __( 'Texture Categories', '3d-product-customizer' ), __( 'Texture Categories', '3d-product-customizer' ), 'manage_options', 'edit-tags.php?taxonomy=dprcu_texture_category&post_type=dprcu_model_texture' ); } add_action( 'admin_menu', 'dprcu_textures_taxonomy_submenu' ); /** * Highlight the parent and submenu items when viewing dprcu_texture_category taxonomy. * * WordPress doesn't auto-highlight the parent menu for taxonomy edit pages, * so we manually set both parent_file and submenu_file to ensure proper menu highlighting. */ function dprcu_highlight_taxonomy_menu( $parent_file ) { global $pagenow, $submenu_file; if ( 'edit-tags.php' === $pagenow ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only context check for menu highlighting, no form processing $taxonomy = ( isset( $_GET['taxonomy'] ) ? sanitize_key( wp_unslash( $_GET['taxonomy'] ) ) : '' ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only context check for menu highlighting, no form processing $post_type = ( isset( $_GET['post_type'] ) ? sanitize_key( wp_unslash( $_GET['post_type'] ) ) : '' ); if ( 'dprcu_texture_category' === $taxonomy && 'dprcu_model_texture' === $post_type ) { $parent_file = 'dprcu-settings'; $submenu_file = 'edit-tags.php?taxonomy=dprcu_texture_category&post_type=dprcu_model_texture'; } } return $parent_file; } add_filter( 'parent_file', 'dprcu_highlight_taxonomy_menu' ); /** * Add a Texture Category filter dropdown to the dprcu_model_texture list table. */ function dprcu_textures_category_filter_dropdown( $post_type ) { if ( 'dprcu_model_texture' !== $post_type ) { return; } $terms = get_terms( array( 'taxonomy' => 'dprcu_texture_category', 'hide_empty' => false, ) ); if ( empty( $terms ) || is_wp_error( $terms ) ) { return; } // Check nonce for security $selected = ''; if ( isset( $_GET['dprcu_texture_nonce'] ) && wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['dprcu_texture_nonce'] ) ), 'dprcu_texture_filter' ) ) { $selected = ( isset( $_GET['dprcu_texture_category'] ) ? sanitize_key( wp_unslash( $_GET['dprcu_texture_category'] ) ) : '' ); } ?> is_main_query() ) { return; } if ( 'dprcu_model_texture' !== $query->get( 'post_type' ) ) { return; } // Check nonce for security if ( !isset( $_GET['dprcu_texture_nonce'] ) || !wp_verify_nonce( sanitize_text_field( wp_unslash( $_GET['dprcu_texture_nonce'] ) ), 'dprcu_texture_filter' ) ) { return; } $cat = ( isset( $_GET['dprcu_texture_category'] ) ? sanitize_key( wp_unslash( $_GET['dprcu_texture_category'] ) ) : '' ); if ( empty( $cat ) ) { return; } $query->set( 'tax_query', array(array( 'taxonomy' => 'dprcu_texture_category', 'field' => 'slug', 'terms' => $cat, )) ); } add_action( 'parse_query', 'dprcu_textures_category_filter_query' ); /** * Add the custom metabox for optional texture maps. */ function dprcu_textures_add_metaboxes() { add_meta_box( 'dprcu_texture_maps', 'Optional Texture Maps', 'dprcu_textures_metabox_html', 'dprcu_model_texture', 'normal', 'default' ); if ( dprcu_fs()?->can_use_premium_code__premium_only() ) { add_meta_box( 'dprcu_texture_preview', __( 'Material Preview', '3d-product-customizer' ), 'dprcu_textures_preview_metabox_html__premium_only', 'dprcu_model_texture', 'side', 'high' ); } else { add_meta_box( 'dprcu_texture_preview', __( 'Material Preview', '3d-product-customizer' ), 'dprcu_textures_preview_metabox_html_free', 'dprcu_model_texture', 'side', 'high' ); } add_meta_box( 'dprcu_texture_color_swatch', __( 'Color Swatch', '3d-product-customizer' ), 'dprcu_textures_color_swatch_metabox_html', 'dprcu_model_texture', 'side', 'default' ); } add_action( 'add_meta_boxes', 'dprcu_textures_add_metaboxes' ); /** * Render the Material Preview metabox (free — upsell). */ function dprcu_textures_preview_metabox_html_free() { $upgrade_url = dprcu_fs()->get_upgrade_url(); ?> ID, '_dprcu_texture_color', true ) ?: '' ); ?>

ID, '_dprcu_texture_roughness_id', true ); $normal_id = get_post_meta( $post->ID, '_dprcu_texture_normal_id', true ); $roughness_url = ( $roughness_id ? wp_get_attachment_image_url( $roughness_id, 'thumbnail' ) : '' ); $normal_url = ( $normal_id ? wp_get_attachment_image_url( $normal_id, 'thumbnail' ) : '' ); // Slider values $roughness = get_post_meta( $post->ID, '_dprcu_texture_roughness', true ); if ( '' === $roughness ) { $roughness = '0.5'; // Default } $metalness = get_post_meta( $post->ID, '_dprcu_texture_metalness', true ); if ( '' === $metalness ) { $metalness = '0.0'; // Default Plastic } $env_intensity = get_post_meta( $post->ID, '_dprcu_texture_env_intensity', true ); if ( '' === $env_intensity ) { $env_intensity = '1.0'; // Default } $disp_scale = get_post_meta( $post->ID, '_dprcu_texture_disp_scale', true ); if ( '' === $disp_scale ) { $disp_scale = '0.01'; // Default } ?>



can_use_premium_code__premium_only() ) { ?> $roughness_id, 'roughness_url' => $roughness_url, 'normal_id' => $normal_id, 'normal_url' => $normal_url, 'env_intensity' => $env_intensity, 'disp_scale' => $disp_scale, ); dprcu_textures_render_advanced_material__premium_only( $post, $data ); ?> __( 'Advanced Material Controls', '3d-product-customizer' ), 'free_features' => array( __( 'Upload 3D model (.glb / .gltf)', '3d-product-customizer' ), __( 'Base color texture per material', '3d-product-customizer' ), __( 'Roughness & metalness value sliders', '3d-product-customizer' ), __( 'Customization steps (1 texture per option)', '3d-product-customizer' ), __( 'Viewer Only Mode (model in product gallery)', '3d-product-customizer' ), __( 'Camera, background & zoom settings', '3d-product-customizer' ), __( 'HDR environment map support', '3d-product-customizer' ) ), 'premium_features' => array( __( 'Multiple textures per option', '3d-product-customizer' ), __( 'Bulk Add Materials per step', '3d-product-customizer' ), __( 'Show / Hide mesh controls per option', '3d-product-customizer' ), __( 'Roughness Map texture', '3d-product-customizer' ), __( 'Normal Map texture', '3d-product-customizer' ), __( 'Ambient Occlusion (AO) Map', '3d-product-customizer' ), __( 'Metalness Map texture', '3d-product-customizer' ), __( 'Displacement Map texture', '3d-product-customizer' ), __( 'Reflection Intensity control', '3d-product-customizer' ), __( 'Displacement Scale control', '3d-product-customizer' ) ), ) ); } ?>
post_parent !== $post_id ) { // Update the attachment's parent wp_update_post( array( 'ID' => $attachment_id, 'post_parent' => $post_id, ) ); } } }; // ------------------------------------ // Save Roughness ID and attach it. // absint() is correct here — attachment IDs are always positive integers. if ( isset( $_POST['dprcu_texture_roughness_id'] ) ) { $roughness_id = absint( wp_unslash( $_POST['dprcu_texture_roughness_id'] ) ); update_post_meta( $post_id, '_dprcu_texture_roughness_id', $roughness_id ); $attach_image( $roughness_id, $post_id ); } // Save Normal ID and attach it. if ( isset( $_POST['dprcu_texture_normal_id'] ) ) { $normal_id = absint( wp_unslash( $_POST['dprcu_texture_normal_id'] ) ); update_post_meta( $post_id, '_dprcu_texture_normal_id', $normal_id ); $attach_image( $normal_id, $post_id ); } // Save AO ID and attach it. if ( isset( $_POST['dprcu_texture_ao_id'] ) ) { $ao_id = absint( wp_unslash( $_POST['dprcu_texture_ao_id'] ) ); update_post_meta( $post_id, '_dprcu_texture_ao_id', $ao_id ); $attach_image( $ao_id, $post_id ); } // Save Metalness ID and attach it. if ( isset( $_POST['dprcu_texture_metalness_id'] ) ) { $metalness_id = absint( wp_unslash( $_POST['dprcu_texture_metalness_id'] ) ); update_post_meta( $post_id, '_dprcu_texture_metalness_id', $metalness_id ); $attach_image( $metalness_id, $post_id ); } // Save Displacement ID and attach it. if ( isset( $_POST['dprcu_texture_displacement_id'] ) ) { $displacement_id = absint( wp_unslash( $_POST['dprcu_texture_displacement_id'] ) ); update_post_meta( $post_id, '_dprcu_texture_displacement_id', $displacement_id ); $attach_image( $displacement_id, $post_id ); } if ( isset( $_POST['dprcu_texture_roughness'] ) ) { update_post_meta( $post_id, '_dprcu_texture_roughness', floatval( wp_unslash( $_POST['dprcu_texture_roughness'] ) ) ); } if ( isset( $_POST['dprcu_texture_metalness'] ) ) { update_post_meta( $post_id, '_dprcu_texture_metalness', floatval( wp_unslash( $_POST['dprcu_texture_metalness'] ) ) ); } if ( isset( $_POST['dprcu_texture_env_intensity'] ) ) { update_post_meta( $post_id, '_dprcu_texture_env_intensity', floatval( wp_unslash( $_POST['dprcu_texture_env_intensity'] ) ) ); } if ( isset( $_POST['dprcu_texture_disp_scale'] ) ) { update_post_meta( $post_id, '_dprcu_texture_disp_scale', floatval( wp_unslash( $_POST['dprcu_texture_disp_scale'] ) ) ); } if ( isset( $_POST['dprcu_texture_color'] ) ) { $color = sanitize_hex_color( wp_unslash( $_POST['dprcu_texture_color'] ) ); update_post_meta( $post_id, '_dprcu_texture_color', ( $color ? $color : '' ) ); } } add_action( 'save_post', 'dprcu_textures_save_postdata' ); /** * Add admin columns for 'baseColor' and re-order. */ function dprcu_admin_columns( $columns ) { $new_columns = array(); $new_columns['cb'] = $columns['cb']; // Checkbox $new_columns['base_color'] = __( 'Base Color', '3d-product-customizer' ); // Custom column $new_columns['title'] = $columns['title']; // Title $new_columns['date'] = $columns['date']; // Date return $new_columns; } add_filter( 'manage_dprcu_model_texture_posts_columns', 'dprcu_admin_columns' ); /** * Display content for the custom 'base_color' column. */ function dprcu_custom_column_content( $column, $post_id ) { if ( 'base_color' === $column ) { if ( has_post_thumbnail( $post_id ) ) { echo get_the_post_thumbnail( $post_id, array(80, 80) ); } else { $color = get_post_meta( $post_id, '_dprcu_texture_color', true ); if ( $color ) { echo '
' . esc_html__( 'HEX', '3d-product-customizer' ) . '
'; } else { echo esc_html__( '—', '3d-product-customizer' ); } } } } add_action( 'manage_dprcu_model_texture_posts_custom_column', 'dprcu_custom_column_content', 10, 2 ); /** * Enqueue admin CSS for the grid view. */ function dprcu_admin_grid_styles( $hook ) { global $post_type; if ( 'edit.php' === $hook && 'dprcu_model_texture' === $post_type || 'dprcu_model_texture' === get_post_type() && in_array( $hook, array('post-new.php', 'post.php'), true ) ) { wp_enqueue_style( 'dprcu-textures-grid-view', plugin_dir_url( __FILE__ ) . 'admin/css/textures-grid.css', array(), '1.0' ); } } add_action( 'admin_enqueue_scripts', 'dprcu_admin_grid_styles' ); /** * Hide 'dprcu_model_texture'Featured Images from the main Media Library. * * This function modifies the main WordPress media query to exclude any * image that is "attached" (i.e., set as a Featured Image) to a * 'dprcu_model_texture' post. */ function dprcu_hide_from_media_library( $query ) { // 1. Only run in the admin area. if ( !is_admin() ) { return; } $is_media_library_grid = false; // 2. Safely check for the main grid screen. // get_current_screen() is not available on AJAX requests, // so we must check if the function exists first. if ( function_exists( 'get_current_screen' ) ) { $screen = get_current_screen(); $is_media_library_grid = $screen && 'upload' === $screen->id && $query->is_main_query(); } // 3. Check for the "Add Media" modal (which is an AJAX query). $is_media_library_modal = 'query-attachments' === $query->get( 'action' ); // 4. If we are on either screen, apply the filter. if ( $is_media_library_grid || $is_media_library_modal ) { // Get all post IDs from your Model Textures CPT $texture_post_ids = get_posts( array( 'post_type' => 'dprcu_model_texture', 'posts_per_page' => -1, 'fields' => 'ids', 'post_status' => 'any', ) ); if ( !empty( $texture_post_ids ) ) { // Tell the query to *exclude* any attachment whose parent // is one of our model_texture posts. $query->set( 'post_parent__not_in', $texture_post_ids ); } } } add_action( 'pre_get_posts', 'dprcu_hide_from_media_library' );