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 () { register_block_type_from_metadata( plugin_dir_path( __FILE__ ) . 'blocks/customizer', [ 'render_callback' => 'dprcu_block_render', ] ); } ); /** * 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'; /** * 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() { 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 ); if ( empty( $model_url ) ) { return; } if ( '1' === $viewer_only ) { dprcu_viewer_canvas_html(); } else { dprcu_html_output(); } } add_action( 'dprcu_display_customizer', 'dprcu_display_customizer_hook' ); /** * Shortcode handler for displaying the 3D customizer. * * Usage: [dprcu-customizer] * * Can be used on any page to display the customizer for the current product. * * @since 3.4.0 * @return string The customizer HTML output. */ function dprcu_customizer_shortcode() { 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 ); if ( empty( $model_url ) ) { return ''; } // Only start buffer after all early returns are done ob_start(); if ( '1' === $viewer_only ) { dprcu_viewer_canvas_html(); } else { dprcu_html_output(); } return ob_get_clean() ?? ''; } add_shortcode( 'dprcu-customizer', 'dprcu_customizer_shortcode' ); function dprcu_block_render( $attributes, $content ) { 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 ); if ( empty( $model_url ) ) { return ''; } ob_start(); if ( '1' === $viewer_only ) { dprcu_viewer_canvas_html(); } else { dprcu_html_output(); } 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() ) { ?>

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(), '1.0' ); wp_enqueue_script( 'dprcu-customizer-bundle', plugin_dir_url( __FILE__ ) . 'dist/customizer.bundle.js', array(), '1.0', true ); // Premium functions are handled via Freemius comment wrappers in the bundle // Free versions will have /* */ sections stripped by Freemius $final_config_for_js = array(); if ( $is_customizer ) { $config_data = json_decode( $config_json, true ); if ( is_array( $config_data ) ) { foreach ( $config_data as $step_id => $step ) { $valid_options = array(); $step_targets = $step['targets'] ?? array(); if ( !empty( $step['options'] ) ) { foreach ( $step['options'] as $opt_id => $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'; $show_meshes = $option['show'] ?? array(); $hide_meshes = $option['hide'] ?? array(); $opt_price = floatval( $option['price'] ?? 0 ); $has_textures = !empty( $texture_ids ); $has_geometry = !empty( $show_meshes ) || !empty( $hide_meshes ); $opt_icon = $option['icon'] ?? ''; if ( $has_textures ) { foreach ( $texture_ids as $texture_id ) { if ( !$texture_id ) { continue; } $tex_name = ( get_the_title( $texture_id ) ?: '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, ), ); } } 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, '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, ); } } } } $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); // Collect texture post_modified timestamps for cache invalidation $texture_versions = array(); if ( $is_customizer && is_array( $config_data ) ) { foreach ( $config_data as $step ) { if ( !empty( $step['options'] ) ) { foreach ( $step['options'] 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 ) ) ) ); foreach ( $texture_ids as $texture_id ) { $texture_post = get_post( $texture_id ); if ( $texture_post ) { // Get all texture URLs for this post $texture_url = get_post_meta( $texture_id, '_dprcu_texture_url', true ); if ( $texture_url ) { $texture_versions[$texture_url] = strtotime( $texture_post->post_modified ); } } } } } } } $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, ), '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', 'const customizerData = ' . wp_json_encode( $customizer_config ) . ';', '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'), '1.0', true ); // Enqueue texture slider sync script wp_enqueue_script( 'dprcu-texture-sliders', plugin_dir_url( __FILE__ ) . 'admin/js/texture-sliders.js', array('jquery'), '1.0', 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' ) ?: '1.0' ), 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'), '1.0', 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'] ) ); $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; } $surcharge = 0; foreach ( $config as $entry ) { $surcharge += floatval( $entry['price'] ?? 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; } // 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' ); ?>

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' ) ); ?>

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)