set_basename( false, __FILE__ );
} else {
if ( !function_exists( 'dprcu_fs' ) ) {
require_once plugin_dir_path( __FILE__ ) . 'includes/freemius.php';
}
// The free version relies on WordPress.org auto-loading translations (WP 4.6+).
// The premium version runs under a different folder name (3d-product-customizer-premium),
// so WordPress won't auto-load the free text domain & load it explicitly instead.
// We also load the premium slug as an alias pointing to the same translation files
// so that the Text Domain header in the premium plugin file matches PCP expectations.
if ( strpos( plugin_basename( __FILE__ ), '3d-product-customizer/' ) === false ) {
add_action( 'init', function () {
$lang_dir = dirname( plugin_basename( __FILE__ ) ) . '/languages';
load_plugin_textdomain( '3d-product-customizer', false, $lang_dir );
// phpcs:ignore PluginCheck.CodeAnalysis.DiscouragedFunctions.load_plugin_textdomainFound -- Required: premium runs under a different folder and is not auto-loaded by WordPress.org.
load_plugin_textdomain( '3d-customizer-premium', false, $lang_dir );
// phpcs:ignore PluginCheck.CodeAnalysis.DiscouragedFunctions.load_plugin_textdomainFound -- Alias for the premium Text Domain header.
} );
}
// Init customizer block for block-based themes (FSE)
add_action( 'init', function () {
$block_dir = plugin_dir_path( __FILE__ ) . 'blocks/customizer';
$asset_file = $block_dir . '/index.asset.php';
$asset = ( file_exists( $asset_file ) ? require $asset_file : array(
'dependencies' => array('wp-blocks', 'wp-element'),
'version' => DPRCU_PLUGIN_VERSION,
) );
wp_register_script(
'dprcu-customizer-block-editor',
plugin_dir_url( __FILE__ ) . 'blocks/customizer/index.js',
$asset['dependencies'],
$asset['version'],
true
);
register_block_type( $block_dir . '/block.json', array(
'render_callback' => 'dprcu_block_render',
'editor_script' => 'dprcu-customizer-block-editor',
) );
} );
/**
* Check if WooCommerce is active. If not, show admin notice and deactivate plugin.
*/
if ( !function_exists( 'is_plugin_active' ) ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
if ( !is_plugin_active( 'woocommerce/woocommerce.php' ) ) {
add_action( 'admin_notices', function () {
echo '
';
echo esc_html__( '3D Product Customizer & Configurator requires WooCommerce to be installed and activated.', '3d-product-customizer' );
echo '
';
} );
return;
}
require_once plugin_dir_path( __FILE__ ) . 'admin/plugin-settings.php';
require_once plugin_dir_path( __FILE__ ) . 'cpt-model-textures.php';
require_once plugin_dir_path( __FILE__ ) . 'admin/product-metabox.php';
//
if ( dprcu_fs()?->is__premium_only() ) {
require_once plugin_dir_path( __FILE__ ) . 'includes/analytics__premium_only.php';
}
//
/**
* Registers the correct output hook for this product page.
*
* Runs at template_redirect & after the queried object is known but
* before any template rendering & so hook setup is complete and ordered
* with no risk of registering hooks from inside another hook callback.
*
* Viewer-only mode uses render_block instead of woocommerce_before_single_product_summary
* because block-based (FSE) themes render the gallery via the
* woocommerce/product-image-gallery Gutenberg block inside .
* Classic WooCommerce action hooks fire outside in FSE themes,
* so they cannot place output inside the gallery column.
*/
function dprcu_setup_product_hooks() {
if ( !function_exists( 'is_product' ) || !is_product() ) {
return;
}
$product_id = get_queried_object_id();
$model_url = get_post_meta( $product_id, '_dprcu_3d_model_url', true );
$viewer_only = get_post_meta( $product_id, '_dprcu_3d_viewer_only', true );
if ( empty( $model_url ) ) {
return;
}
/**
* Fires when product hooks are being set up for the customizer.
*
* @since 3.4.0
* @param int $product_id The current product ID.
* @param string $model_url The 3D model URL.
* @param string $viewer_only Whether viewer-only mode is enabled.
*/
do_action(
'dprcu_setup_product_hooks',
$product_id,
$model_url,
$viewer_only
);
$settings = get_option( 'dprcu_settings' );
$display_method = $settings['display_method'] ?? 'auto';
// Only auto-register hooks if display method is set to 'auto'
if ( 'auto' !== $display_method ) {
return;
}
if ( '1' === $viewer_only ) {
// Use render_block to intercept the gallery block in FSE/block themes.
add_filter(
'render_block',
'dprcu_intercept_gallery_block',
10,
2
);
} else {
add_action( 'woocommerce_before_single_product_summary', 'dprcu_html_output', 5 );
}
}
add_action( 'template_redirect', 'dprcu_setup_product_hooks' );
/**
* Handles manual display of customizer via do_action() hook.
*
* Allows themes and plugins to display the customizer by calling:
* do_action('dprcu_display_customizer')
*
* @since 3.4.0
*/
function dprcu_display_customizer_hook( $product_id = null ) {
if ( !$product_id ) {
if ( !function_exists( 'is_product' ) || !is_product() ) {
return;
}
$product_id = get_the_ID();
}
$model_url = get_post_meta( $product_id, '_dprcu_3d_model_url', true );
$viewer_only = get_post_meta( $product_id, '_dprcu_3d_viewer_only', true );
if ( empty( $model_url ) ) {
return;
}
if ( '1' === $viewer_only ) {
dprcu_viewer_canvas_html( $product_id );
} else {
dprcu_html_output( $product_id );
}
}
add_action(
'dprcu_display_customizer',
'dprcu_display_customizer_hook',
10,
1
);
/**
* Shortcode handler for displaying the 3D customizer.
*
* Usage: [dprcu-customizer id="123"]
*
* @since 3.4.0
* @param array $atts Shortcode attributes.
* @return string The customizer HTML output.
*/
function dprcu_customizer_shortcode( $atts ) {
$atts = shortcode_atts( array(
'id' => 0,
), $atts, 'dprcu-customizer' );
$product_id = intval( $atts['id'] );
if ( $product_id ) {
$product = wc_get_product( $product_id );
if ( !$product ) {
return '';
}
} else {
if ( !function_exists( 'is_product' ) || !is_product() ) {
return '';
}
$product_id = get_the_ID();
}
$model_url = get_post_meta( $product_id, '_dprcu_3d_model_url', true );
$viewer_only = get_post_meta( $product_id, '_dprcu_3d_viewer_only', true );
if ( empty( $model_url ) ) {
return '';
}
dprcu_enqueue_scripts( $product_id );
ob_start();
if ( '1' === $viewer_only ) {
dprcu_viewer_canvas_html( $product_id );
} else {
dprcu_html_output( $product_id );
}
return ob_get_clean() ?? '';
}
add_shortcode( 'dprcu-customizer', 'dprcu_customizer_shortcode' );
function dprcu_block_render( $attributes, $content ) {
$product_id = ( !empty( $attributes['productId'] ) ? intval( $attributes['productId'] ) : 0 );
if ( !$product_id ) {
if ( !function_exists( 'is_product' ) || !is_product() ) {
return '';
}
$product_id = get_the_ID();
}
$model_url = get_post_meta( $product_id, '_dprcu_3d_model_url', true );
$viewer_only = get_post_meta( $product_id, '_dprcu_3d_viewer_only', true );
if ( empty( $model_url ) ) {
return '';
}
dprcu_enqueue_scripts( $product_id );
ob_start();
if ( '1' === $viewer_only ) {
dprcu_viewer_canvas_html( $product_id );
} else {
dprcu_html_output( $product_id );
}
return ob_get_clean() ?? '';
}
/**
* Remove wpautop-generated markers from shortcode output.
*
* Hooked to 'the_content' at priority 12 (after wpautop at priority 10).
* This preserves the shortcode output without wpautop interference.
*/
function dprcu_remove_shortcode_markers( $content ) {
// Remove the temporary markers
$content = str_replace( '', '', $content );
$content = str_replace( '', '', $content );
return $content;
}
add_filter( 'the_content', 'dprcu_remove_shortcode_markers', 12 );
/**
* Replaces the WooCommerce product image gallery block with the 3D viewer canvas
* when the product has Viewer Only Mode enabled.
*
* Hooked to render_block so it works in both classic and block-based (FSE) themes.
*
* @param string $block_content The rendered block HTML.
* @param array $block The block data array.
* @return string
*/
function dprcu_intercept_gallery_block( $block_content, $block ) {
if ( 'woocommerce/product-image-gallery' !== ($block['blockName'] ?? '') ) {
return $block_content;
}
ob_start();
dprcu_viewer_canvas_html();
return ob_get_clean();
}
function dprcu_render_ar_button( $mode = 'simple' ) {
?>
ID;
}
$config_json = get_post_meta( $product_id, '_dprcu_customizer_config', true );
$price = get_post_meta( $product_id, '_price', true );
$ar_display_mode = get_option( 'dprcu_settings' )['ar_display_mode'] ?? 'simple';
if ( empty( $config_json ) ) {
return;
}
$config_data = json_decode( $config_json, true );
$has_valid_options = false;
if ( is_array( $config_data ) ) {
foreach ( $config_data as $step ) {
if ( !empty( $step['options'] ) ) {
$has_valid_options = true;
break;
}
}
}
if ( !$has_valid_options ) {
return;
}
/**
* Fires before the customizer HTML output is rendered.
*
* @since 3.4.0
* @param int $product_id The current product ID.
* @param array $config_data The decoded customizer configuration.
*/
do_action( 'dprcu_before_customizer_output', $product_id, $config_data );
?>
can_use_premium_code__premium_only() ) {
dprcu_render_hotspot_toggle__premium_only();
}
if ( 'simple' === $ar_display_mode ) {
dprcu_render_ar_button();
}
?>
Ctrl + Z
Ctrl + Y
R
+
-
F
B
is__premium_only() ) {
?>
last-modified-timestamp map used for front-end cache busting.
*
* Primes the post/meta object cache for every texture (and the attachments
* their maps point to) before reading them, then walks the step/option tree
* exactly once to build both return values together.
*
* @param string $config_json Raw `_dprcu_customizer_config` postmeta JSON.
* @return array{0: array, 1: array} [$final_config_for_js, $texture_versions]
*/
function dprcu_build_customizer_options( $config_json ) {
$config_data = json_decode( $config_json, true );
if ( !is_array( $config_data ) ) {
return array(array(), array());
}
$texture_ids_to_prime = array();
foreach ( $config_data as $step ) {
foreach ( $step['options'] ?? array() as $option ) {
$raw_ids = $option['texture_ids'] ?? $option['texture_id'] ?? '';
$ids = ( is_array( $raw_ids ) ? $raw_ids : array_filter( array_map( 'intval', explode( ',', $raw_ids ) ) ) );
$texture_ids_to_prime = array_merge( $texture_ids_to_prime, $ids );
}
}
$texture_ids_to_prime = array_unique( array_filter( $texture_ids_to_prime ) );
if ( !empty( $texture_ids_to_prime ) ) {
_prime_post_caches( $texture_ids_to_prime, false, true );
$attachment_ids_to_prime = array();
foreach ( $texture_ids_to_prime as $texture_id ) {
$attachment_ids_to_prime[] = get_post_thumbnail_id( $texture_id );
$attachment_ids_to_prime[] = get_post_meta( $texture_id, '_dprcu_texture_roughness_id', true );
$attachment_ids_to_prime[] = get_post_meta( $texture_id, '_dprcu_texture_normal_id', true );
$attachment_ids_to_prime[] = get_post_meta( $texture_id, '_dprcu_texture_ao_id', true );
$attachment_ids_to_prime[] = get_post_meta( $texture_id, '_dprcu_texture_metalness_id', true );
$attachment_ids_to_prime[] = get_post_meta( $texture_id, '_dprcu_texture_displacement_id', true );
}
$attachment_ids_to_prime = array_unique( array_filter( array_map( 'intval', $attachment_ids_to_prime ) ) );
if ( !empty( $attachment_ids_to_prime ) ) {
_prime_post_caches( $attachment_ids_to_prime, false, false );
}
}
$final_config_for_js = array();
$texture_versions = array();
foreach ( $config_data as $step_id => $step ) {
$valid_options = array();
$step_targets = $step['targets'] ?? array();
foreach ( $step['options'] ?? array() as $option ) {
$texture_ids_raw = $option['texture_ids'] ?? $option['texture_id'] ?? '';
$texture_ids = ( is_array( $texture_ids_raw ) ? $texture_ids_raw : array_filter( array_map( 'intval', explode( ',', $texture_ids_raw ) ) ) );
$opt_name = $option['name'] ?? 'Option';
$opt_price = floatval( $option['price'] ?? 0 );
$opt_icon = $option['icon'] ?? '';
$show_meshes = array();
$hide_meshes = array();
$show_steps = array();
$hide_steps = array();
/* */
$show_meshes = $option['show'] ?? array();
$hide_meshes = $option['hide'] ?? array();
$show_steps = array_values( $option['show_steps'] ?? array() );
$hide_steps = array_values( $option['hide_steps'] ?? array() );
/* */
$has_textures = !empty( $texture_ids );
$has_geometry = !empty( $show_meshes ) || !empty( $hide_meshes );
if ( $has_textures ) {
foreach ( $texture_ids as $texture_id ) {
if ( !$texture_id ) {
continue;
}
$texture_post = get_post( $texture_id );
$tex_name = ( $texture_post ? ( $texture_post->post_title ?: 'Material' ) : 'Material' );
$valid_options[] = array(
'name' => $tex_name,
'icon' => $opt_icon,
'type' => 'material',
'price' => $opt_price,
'value' => array(
'texture' => ( get_the_post_thumbnail_url( $texture_id, 'full' ) ?: null ),
'textureThumbnail' => ( get_the_post_thumbnail_url( $texture_id, 'thumbnail' ) ?: null ),
'color' => ( get_post_meta( $texture_id, '_dprcu_texture_color', true ) ?: null ),
'roughnessMap' => ( wp_get_attachment_url( get_post_meta( $texture_id, '_dprcu_texture_roughness_id', true ) ) ?: null ),
'normalMap' => ( wp_get_attachment_url( get_post_meta( $texture_id, '_dprcu_texture_normal_id', true ) ) ?: null ),
'aoMap' => ( wp_get_attachment_url( get_post_meta( $texture_id, '_dprcu_texture_ao_id', true ) ) ?: null ),
'metalnessMap' => ( wp_get_attachment_url( get_post_meta( $texture_id, '_dprcu_texture_metalness_id', true ) ) ?: null ),
'displacementMap' => ( wp_get_attachment_url( get_post_meta( $texture_id, '_dprcu_texture_displacement_id', true ) ) ?: null ),
'roughness' => floatval( get_post_meta( $texture_id, '_dprcu_texture_roughness', true ) ?? '0.9' ),
'metalness' => floatval( get_post_meta( $texture_id, '_dprcu_texture_metalness', true ) ?? '0.1' ),
'displacementScale' => floatval( get_post_meta( $texture_id, '_dprcu_texture_disp_scale', true ) ?? '0.0' ),
'envIntensity' => floatval( get_post_meta( $texture_id, '_dprcu_texture_env_intensity', true ) ?? '1.0' ),
'show' => $show_meshes,
'hide' => $hide_meshes,
'showSteps' => $show_steps,
'hideSteps' => $hide_steps,
),
);
$texture_url = get_post_meta( $texture_id, '_dprcu_texture_url', true );
if ( $texture_url && $texture_post ) {
$texture_versions[$texture_url] = strtotime( $texture_post->post_modified );
}
}
} elseif ( $has_geometry ) {
$valid_options[] = array(
'name' => $opt_name,
'icon' => $opt_icon,
'type' => 'object',
'price' => $opt_price,
'value' => array(
'show' => $show_meshes,
'hide' => $hide_meshes,
'showSteps' => $show_steps,
'hideSteps' => $hide_steps,
'textureThumbnail' => plugin_dir_url( __FILE__ ) . 'assets/geometry-icon.png',
),
);
}
}
if ( !empty( $valid_options ) ) {
$final_config_for_js[$step_id] = array(
'name' => $step['name'] ?? 'Untitled Step',
'description' => $step['description'] ?? '',
'icon' => $step['icon'] ?? '',
'parts' => $step_targets,
'options' => $valid_options,
);
}
}
return array($final_config_for_js, $texture_versions);
}
/**
* Enqueues the front-end bundled JavaScript, styles, and passes data.
*
* Activates in both viewer-only mode (model URL only) and full customizer
* mode (model URL + step config). Passes a `viewerOnly` boolean so the JS
* bundle can skip UI rendering when no steps are configured.
*/
function dprcu_enqueue_scripts( $product_id = null ) {
static $enqueued = array();
$cache_key = ( $product_id ? (int) $product_id : 0 );
if ( isset( $enqueued[$cache_key] ) ) {
return;
}
$enqueued[$cache_key] = true;
if ( !$product_id ) {
if ( !function_exists( 'is_product' ) || !is_product() ) {
return;
}
global $post;
$product_id = $post->ID;
}
$model_url = get_post_meta( $product_id, '_dprcu_3d_model_url', true );
$viewer_only = get_post_meta( $product_id, '_dprcu_3d_viewer_only', true );
//
$hotspot_enabled = get_option( 'dprcu_settings' )['hotspot_enabled'] ?? true;
//
$config_json = get_post_meta( $product_id, '_dprcu_customizer_config', true );
$is_viewer_only = '1' === $viewer_only && !empty( $model_url );
$is_customizer = !empty( $config_json ) && !empty( $model_url );
if ( !$is_viewer_only && !$is_customizer ) {
return;
}
/**
* Fires before scripts and styles are enqueued for the customizer.
*
* @since 3.4.0
* @param int $product_id The current product ID.
* @param bool $is_viewer_only Whether this is viewer-only mode.
* @param bool $is_customizer Whether customizer is active.
*/
do_action(
'dprcu_before_enqueue_scripts',
$product_id,
$is_viewer_only,
$is_customizer
);
wp_enqueue_style(
'dprcu-customizer-style',
plugin_dir_url( __FILE__ ) . 'style.css',
array(),
DPRCU_PLUGIN_VERSION
);
wp_enqueue_script(
'dprcu-customizer-bundle',
plugin_dir_url( __FILE__ ) . 'dist/customizer.bundle.js',
array(),
DPRCU_PLUGIN_VERSION,
true
);
list( $final_config_for_js, $texture_versions ) = ( $is_customizer ? dprcu_build_customizer_options( $config_json ) : array(array(), array()) );
$global_settings = get_option( 'dprcu_settings' );
$bg_colors_str = $global_settings['bg_colors'] ?? "#f0f0f0\n#ffffff\n#222222";
$bg_colors = preg_split( '/\\r\\n|[\\r\\n]/', $bg_colors_str );
$zoom_limits = $global_settings['zoom_limits'] ?? array(1, 10);
$hdr_file = $global_settings['hdr_file'] ?? '';
$cam_dist_mult = (float) ($global_settings['camera_distance_multiplier'] ?? 1.2);
$logo_id = get_theme_mod( 'custom_logo' );
$logo_url = ( $logo_id ? wp_get_attachment_image_url( $logo_id, 'full' ) : get_site_icon_url() );
$camera_position_raw = get_post_meta( $product_id, '_dprcu_camera_position', true );
$camera_position = ( $camera_position_raw ? json_decode( $camera_position_raw, true ) : null );
$customizer_config = array(
'productId' => $product_id,
'ajaxUrl' => admin_url( 'admin-ajax.php' ),
'modelUrl' => esc_url( $model_url ),
'viewerOnly' => $is_viewer_only,
'customizationOptions' => $final_config_for_js,
'textureVersions' => $texture_versions,
'cameraPosition' => $camera_position,
'currency' => ( function_exists( 'get_woocommerce_currency' ) ? get_woocommerce_currency() : 'USD' ),
'currencySymbol' => ( function_exists( 'get_woocommerce_currency_symbol' ) ? html_entity_decode( get_woocommerce_currency_symbol(), ENT_QUOTES, 'UTF-8' ) : '$' ),
'currencyPosition' => get_option( 'woocommerce_currency_pos', 'left' ),
'siteLogoUrl' => ( $logo_url ? esc_url( $logo_url ) : '' ),
'settings' => array(
'hotspotEnabled' => $hotspot_enabled,
'hdrFile' => esc_url( $hdr_file ),
'cameraDistanceMultiplier' => $cam_dist_mult,
'bgColors' => array_values( array_filter( $bg_colors ) ),
'zoomLimits' => $zoom_limits,
'stepThumbnailsEnabled' => ( isset( $global_settings['step_thumbnails_enabled'] ) ? (bool) $global_settings['step_thumbnails_enabled'] : false ),
),
'analyticsEnabled' => ( isset( $global_settings['analytics_enabled'] ) ? (bool) $global_settings['analytics_enabled'] : true ),
'analyticsUrl' => rest_url( 'dprcu/v1/track' ),
'i18n' => array(
'copied' => __( 'Copied!', '3d-product-customizer' ),
'shareDefaultMsg' => __( 'Check out my custom design!', '3d-product-customizer' ),
'adding' => __( 'Adding...', '3d-product-customizer' ),
'added' => __( 'Added!', '3d-product-customizer' ),
'loadingTexture' => __( 'Loading Texture...', '3d-product-customizer' ),
'loadingModel' => __( 'Loading 3D Model...', '3d-product-customizer' ),
'generatingPdf' => __( 'Generating PDF...', '3d-product-customizer' ),
'productIdMissing' => __( 'Product ID missing', '3d-product-customizer' ),
'couldNotAddToCart' => __( 'Could not add to cart.', '3d-product-customizer' ),
'productConfiguration' => __( 'Product Configuration', '3d-product-customizer' ),
'selectedOptions' => __( 'Selected Options', '3d-product-customizer' ),
'pluginPage' => __( 'Plugin Page', '3d-product-customizer' ),
'docs' => __( 'Docs', '3d-product-customizer' ),
'demo' => __( 'Demo', '3d-product-customizer' ),
),
);
/**
* Filters the customizer configuration data passed to JavaScript.
*
* @since 3.4.0
* @param array $customizer_config The configuration array.
* @param int $product_id The current product ID.
* @param bool $is_viewer_only Whether this is viewer-only mode.
* @return array The filtered configuration array.
*/
$customizer_config = apply_filters(
'dprcu_customizer_config',
$customizer_config,
$product_id,
$is_viewer_only
);
wp_add_inline_script( 'dprcu-customizer-bundle', 'var customizerData = ' . wp_json_encode( $customizer_config, JSON_UNESCAPED_UNICODE ) . ';', 'before' );
/**
* Fires after scripts and styles are enqueued for the customizer.
*
* @since 3.4.0
* @param int $product_id The current product ID.
* @param bool $is_viewer_only Whether this is viewer-only mode.
* @param bool $is_customizer Whether customizer is active.
* @param array $customizer_config The customizer configuration array.
*/
do_action(
'dprcu_after_enqueue_scripts',
$product_id,
$is_viewer_only,
$is_customizer,
$customizer_config
);
}
add_action( 'wp_enqueue_scripts', 'dprcu_enqueue_scripts' );
function dprcu_admin_enqueue_scripts( $hook ) {
global $post_type;
if ( ('post.php' === $hook || 'post-new.php' === $hook) && 'dprcu_model_texture' === $post_type ) {
wp_enqueue_media();
wp_enqueue_style( 'wp-color-picker' );
wp_enqueue_script(
'dprcu-customizer-admin-textures',
plugin_dir_url( __FILE__ ) . 'admin/js/cpt-textures.js',
array('jquery', 'wp-color-picker'),
DPRCU_PLUGIN_VERSION,
true
);
// Enqueue texture slider sync script
wp_enqueue_script(
'dprcu-texture-sliders',
plugin_dir_url( __FILE__ ) . 'admin/js/texture-sliders.js',
array('jquery'),
DPRCU_PLUGIN_VERSION,
true
);
if ( dprcu_fs()?->can_use_premium_code__premium_only() ) {
$post_id = get_the_ID();
$roughness = get_post_meta( $post_id, '_dprcu_texture_roughness', true );
$metalness = get_post_meta( $post_id, '_dprcu_texture_metalness', true );
$env_intensity = get_post_meta( $post_id, '_dprcu_texture_env_intensity', true );
$disp_scale = get_post_meta( $post_id, '_dprcu_texture_disp_scale', true );
$roughness_id = get_post_meta( $post_id, '_dprcu_texture_roughness_id', true );
$normal_id = get_post_meta( $post_id, '_dprcu_texture_normal_id', true );
$ao_id = get_post_meta( $post_id, '_dprcu_texture_ao_id', true );
$metalness_id = get_post_meta( $post_id, '_dprcu_texture_metalness_id', true );
$displacement_id = get_post_meta( $post_id, '_dprcu_texture_displacement_id', true );
$global_settings = get_option( 'dprcu_settings', array() );
wp_enqueue_script(
'dprcu-material-preview',
plugin_dir_url( __FILE__ ) . 'dist/materialPreview.bundle.js',
array(),
( filemtime( plugin_dir_path( __FILE__ ) . 'dist/materialPreview.bundle.js' ) ?: DPRCU_PLUGIN_VERSION ),
true
);
wp_localize_script( 'dprcu-material-preview', 'dprcu_material_preview', array(
'textureUrl' => ( get_the_post_thumbnail_url( $post_id, 'full' ) ? get_the_post_thumbnail_url( $post_id, 'full' ) : '' ),
'color' => ( get_post_meta( $post_id, '_dprcu_texture_color', true ) ? get_post_meta( $post_id, '_dprcu_texture_color', true ) : '' ),
'roughness' => ( '' !== $roughness ? $roughness : '0.5' ),
'metalness' => ( '' !== $metalness ? $metalness : '0.0' ),
'envIntensity' => ( '' !== $env_intensity ? $env_intensity : '1.0' ),
'dispScale' => ( '' !== $disp_scale ? $disp_scale : '0.0' ),
'roughnessMap' => ( $roughness_id ? wp_get_attachment_url( $roughness_id ) : '' ),
'normalMap' => ( $normal_id ? wp_get_attachment_url( $normal_id ) : '' ),
'aoMap' => ( $ao_id ? wp_get_attachment_url( $ao_id ) : '' ),
'metalnessMap' => ( $metalness_id ? wp_get_attachment_url( $metalness_id ) : '' ),
'displacementMap' => ( $displacement_id ? wp_get_attachment_url( $displacement_id ) : '' ),
'hdrFile' => esc_url( $global_settings['hdr_file'] ?? '' ),
) );
}
}
if ( ('post.php' === $hook || 'post-new.php' === $hook) && 'product' === $post_type ) {
wp_enqueue_script(
'dprcu-admin-model-scanner',
plugin_dir_url( __FILE__ ) . 'dist/adminScanner.bundle.js',
array('jquery'),
DPRCU_PLUGIN_VERSION,
true
);
}
}
add_action( 'admin_enqueue_scripts', 'dprcu_admin_enqueue_scripts' );
/**
* Add custom configuration data to the cart item.
*
* This function fires when an item is added to the cart
* and checks for our 'shoe_config' POST data.
*/
function dprcu_add_cart_item_data( $cart_item_data, $product_id, $variation_id ) {
/**
* Fires before customizer configuration is added to the cart item.
*
* @since 3.4.0
* @param array $cart_item_data The cart item data.
* @param int $product_id The product ID.
* @param int $variation_id The variation ID.
*/
do_action(
'dprcu_before_add_to_cart',
$cart_item_data,
$product_id,
$variation_id
);
if ( isset( $_POST['shoe_config'] ) ) {
// phpcs:ignore WordPress.Security.NonceVerification.Missing
$config_string = sanitize_text_field( wp_unslash( $_POST['shoe_config'] ) );
// phpcs:ignore WordPress.Security.NonceVerification.Missing
$cart_item_data['_dprcu_custom_shoe_config'] = $config_string;
}
/**
* Filters the cart item data before it's added to the cart.
*
* @since 3.4.0
* @param array $cart_item_data The cart item data.
* @param int $product_id The product ID.
* @param int $variation_id The variation ID.
* @return array The filtered cart item data.
*/
$cart_item_data = apply_filters(
'dprcu_cart_item_data',
$cart_item_data,
$product_id,
$variation_id
);
return $cart_item_data;
}
add_filter(
'woocommerce_add_cart_item_data',
'dprcu_add_cart_item_data',
10,
3
);
/**
* Apply option price surcharges to the cart item.
*
* Reads the stored shoe_config JSON, sums the per-option prices,
* and adds them to the base product price.
*/
function dprcu_apply_option_prices( $cart_object ) {
if ( is_admin() && !defined( 'DOING_AJAX' ) ) {
return;
}
foreach ( $cart_object->get_cart() as $cart_item ) {
if ( empty( $cart_item['_dprcu_custom_shoe_config'] ) ) {
continue;
}
$config = json_decode( $cart_item['_dprcu_custom_shoe_config'], true );
if ( !is_array( $config ) ) {
continue;
}
// Build an authoritative price map from server-side meta — never trust client-submitted prices.
$product_config = json_decode( ( get_post_meta( $cart_item['product_id'], '_dprcu_customizer_config', true ) ?: '[]' ), true );
$price_map = [];
if ( is_array( $product_config ) ) {
foreach ( $product_config as $step ) {
$step_name = $step['name'] ?? '';
foreach ( $step['options'] ?? [] as $opt ) {
$price_map[$step_name][$opt['name'] ?? ''] = floatval( $opt['price'] ?? 0 );
}
}
}
$surcharge = 0;
foreach ( $config as $entry ) {
$part = $entry['part'] ?? '';
$option = $entry['option'] ?? '';
$surcharge += $price_map[$part][$option] ?? 0;
}
if ( $surcharge > 0 ) {
$cart_item['data']->set_price( $cart_item['data']->get_price() + $surcharge );
}
}
}
add_action(
'woocommerce_before_calculate_totals',
'dprcu_apply_option_prices',
10,
1
);
/**
* Display the custom configuration in the cart.
*
* This reads the 'shoe_config' JSON and formats it
* as line items for the user to see.
*/
function dprcu_display_cart_item_data( $item_data, $cart_item ) {
/**
* Fires before cart item data is displayed.
*
* @since 3.4.0
* @param array $item_data The item data.
* @param array $cart_item The cart item.
*/
do_action( 'dprcu_before_display_cart_item_data', $item_data, $cart_item );
if ( isset( $cart_item['_dprcu_custom_shoe_config'] ) ) {
$config = json_decode( $cart_item['_dprcu_custom_shoe_config'], true );
if ( is_array( $config ) ) {
foreach ( $config as $item ) {
$item_data[] = array(
'key' => sanitize_text_field( $item['part'] ),
'value' => sanitize_text_field( $item['option'] ),
'display' => sanitize_text_field( $item['option'] ),
);
}
}
}
/**
* Filters the cart item display data.
*
* @since 3.4.0
* @param array $item_data The item data to display.
* @param array $cart_item The cart item.
* @return array The filtered item data.
*/
$item_data = apply_filters( 'dprcu_cart_item_display_data', $item_data, $cart_item );
return $item_data;
}
add_filter(
'woocommerce_get_item_data',
'dprcu_display_cart_item_data',
10,
2
);
/**
* Save the custom configuration to the order line item.
*
* This runs when the order is created.
*/
function dprcu_add_order_item_meta(
$item,
$cart_item_key,
$values,
$order
) {
/**
* Fires before customizer data is added to an order item.
*
* @since 3.4.0
* @param WC_Order_Item $item The order item object.
* @param string $cart_item_key The cart item key.
* @param array $values The cart item values.
* @param WC_Order $order The order object.
*/
do_action(
'dprcu_before_add_order_item_meta',
$item,
$cart_item_key,
$values,
$order
);
if ( isset( $values['_dprcu_custom_shoe_config'] ) ) {
$config = json_decode( $values['_dprcu_custom_shoe_config'], true );
if ( is_array( $config ) ) {
foreach ( $config as $line ) {
$item->add_meta_data( sanitize_text_field( $line['part'] ), sanitize_text_field( $line['option'] ) );
}
}
}
/**
* Fires after customizer data is added to an order item.
*
* @since 3.4.0
* @param WC_Order_Item $item The order item object.
* @param string $cart_item_key The cart item key.
* @param array $values The cart item values.
* @param WC_Order $order The order object.
*/
do_action(
'dprcu_after_add_order_item_meta',
$item,
$cart_item_key,
$values,
$order
);
}
add_action(
'woocommerce_checkout_create_order_line_item',
'dprcu_add_order_item_meta',
10,
4
);
/**
* Renders the watermark/branding HTML.
*/
function dprcu_render_watermark() {
?>
id, 'dprcu' ) === false && 'plugins' !== $screen->id && 'dashboard' !== $screen->id ) {
return;
}
if ( !current_user_can( 'manage_options' ) ) {
return;
}
// Record activation time on first admin visit
$activated_at = get_option( 'dprcu_activated_at' );
if ( !$activated_at ) {
update_option( 'dprcu_activated_at', time() );
return;
}
// Wait at least 1 day after activation before asking
if ( time() - $activated_at < DAY_IN_SECONDS ) {
return;
}
// Permanent dismissal ("Already Reviewed")
if ( get_option( 'dprcu_review_forever' ) ) {
return;
}
// Respect 7-day dismissal
$dismissed_at = get_option( 'dprcu_review_dismissed_at' );
if ( $dismissed_at && time() - $dismissed_at < 7 * DAY_IN_SECONDS ) {
return;
}
$review_url = 'https://wordpress.org/support/plugin/3d-product-customizer/reviews/#new-post';
$dismiss_url = wp_nonce_url( add_query_arg( 'dprcu_review_dismiss', '1' ), 'dprcu_review_dismiss' );
$forever_url = wp_nonce_url( add_query_arg( 'dprcu_review_forever', '1' ), 'dprcu_review_forever' );
?>
3D Product Customizer & Configurator for WooCommerce? Please take a moment to rate it on WordPress.org - your support helps us keep improving!', '3d-product-customizer' ) );
?>
id, 'dprcu' ) === false && 'plugins' !== $screen->id && 'dashboard' !== $screen->id ) {
return;
}
if ( !current_user_can( 'manage_options' ) ) {
return;
}
// Record activation time on first admin visit
$activated_at = get_option( 'dprcu_activated_at' );
if ( !$activated_at ) {
update_option( 'dprcu_activated_at', time() );
return;
}
// Wait at least 1 day after activation before asking
if ( time() - $activated_at < DAY_IN_SECONDS ) {
return;
}
//ask for contact/help every 3 days
$dismissed_at = get_option( 'dprcu_support_dismissed_at' );
if ( $dismissed_at && time() - $dismissed_at < 3 * DAY_IN_SECONDS ) {
return;
}
$dismiss_url = wp_nonce_url( add_query_arg( 'dprcu_support_dismiss', '1' ), 'dprcu_support_dismiss' );
?>
?
documentation or contact page .', '3d-product-customizer' ) );
?>
can_use_premium_code__premium_only();
$links[] = '' . esc_html__( 'Demo', '3d-product-customizer' ) . ' ';
$links[] = '' . esc_html__( 'Docs', '3d-product-customizer' ) . ' ';
if ( !$is_premium ) {
$links[] = '' . esc_html__( 'Buy Premium', '3d-product-customizer' ) . ' ';
}
return $links;
},
20,
2
);
}
// end else (Freemius auto-deactivation guard)