_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'] ) ) : '' );
}
?>
slug );
?>>
name );
?>
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' );