__( '3D Scan & Show', '3d-scan-and-show' ), ), ), $categories ); } public function register_block() { if ( function_exists( 'wp_register_block_types_from_metadata_collection' ) ) { wp_register_block_types_from_metadata_collection( __DIR__ . '/build', __DIR__ . '/build/blocks-manifest.php' ); } else { if ( function_exists( 'wp_register_block_metadata_collection' ) ) { wp_register_block_metadata_collection( __DIR__ . '/build', __DIR__ . '/build/blocks-manifest.php' ); } $manifest_data = require __DIR__ . '/build/blocks-manifest.php'; foreach ( array_keys( $manifest_data ) as $block_type ) { register_block_type( __DIR__ . "/build/{$block_type}" ); } } } public function check_woocommerce() { $screen = get_current_screen(); if ( ! $screen || strpos( $screen->id, '3d-scan-and-show' ) === false ) { return; } if ( ! class_exists( 'WooCommerce' ) ) { ?>

slug === 'single-product' ) { return 'single-product'; } } } // Fallback to single-product return 'single-product'; } /** * Get single-product template slug * * @return array List of template slugs */ private function get_single_templates() { $templates = array(); if ( function_exists( 'get_block_templates' ) ) { $block_templates = get_block_templates( array(), 'wp_template' ); foreach ( $block_templates as $template ) { if ( $template->slug === 'single-product' ) { $templates[] = $template->slug; } } } return $templates; } public function add_admin_menu() { // Load SVG icon data $icon_svg = file_get_contents( plugin_dir_path( __FILE__ ) . 'assets/favicon.svg' ); $icon_data = 'data:image/svg+xml;base64,' . base64_encode( $icon_svg ); add_menu_page( '3D Scan & Show', '3D Scan & Show', 'manage_options', '3d-scan-and-show', array( $this, 'render_main_page' ), $icon_data, 59 // Position after Marketing (58.5) ); add_submenu_page( '3d-scan-and-show', 'Quick Start', 'Quick Start', 'manage_options', '3d-scan-and-show', array( $this, 'render_main_page' ) ); add_submenu_page( '3d-scan-and-show', 'Settings', 'Settings', 'manage_options', '3d-scan-and-show-settings', array( $this, 'render_main_page' ) ); add_submenu_page( '3d-scan-and-show', 'Dashboard', 'Dashboard', 'manage_options', '3d-scan-and-show-dashboard', array( $this, 'render_main_page' ) ); add_submenu_page( '3d-scan-and-show', 'Contact Us', 'Contact Us', 'manage_options', '3d-scan-and-show-contact', array( $this, 'render_main_page' ) ); } /** * Enqueue menu icon styles and scripts for all admin pages * Uses proper WordPress enqueue functions during admin_enqueue_scripts hook * * @param string $hook The current admin page hook suffix. */ public function enqueue_menu_icon_assets( $hook ) { // Enqueue menu icon CSS wp_enqueue_style( 'scanshow-menu-icon', plugin_dir_url( __FILE__ ) . 'admin/css/menu-icon.css', array(), '1.0.0' ); // Enqueue menu icon JS wp_enqueue_script( 'scanshow-menu-icon', plugin_dir_url( __FILE__ ) . 'admin/js/menu-icon.js', array( 'jquery' ), '1.0.0', true ); // Load SVG content and pass to script $icon_svg_path = plugin_dir_path( __FILE__ ) . 'assets/favicon.svg'; if ( file_exists( $icon_svg_path ) ) { $icon_svg = file_get_contents( $icon_svg_path ); wp_localize_script( 'scanshow-menu-icon', 'scanshowMenuIcon', array( 'svg' => $icon_svg ) ); } } public function enqueue_admin_assets( $hook ) { if ( strpos( $hook, '3d-scan-and-show' ) === false ) { return; } // Enqueue CSS files - organized by shared components and page-specific styles // Shared styles (variables, forms, buttons, cards, modal, layout) wp_enqueue_style( 'scanshow-shared', plugin_dir_url( __FILE__ ) . 'admin/css/shared.css', array(), '1.0.0' ); // Page-specific styles wp_enqueue_style( 'scanshow-dashboard', plugin_dir_url( __FILE__ ) . 'admin/css/dashboard.css', array( 'scanshow-shared' ), '1.0.0' ); wp_enqueue_style( 'scanshow-settings', plugin_dir_url( __FILE__ ) . 'admin/css/settings.css', array( 'scanshow-shared' ), '1.0.0' ); wp_enqueue_style( 'scanshow-landing', plugin_dir_url( __FILE__ ) . 'admin/css/landing.css', array( 'scanshow-shared' ), '1.0.0' ); wp_enqueue_style( 'scanshow-contact', plugin_dir_url( __FILE__ ) . 'admin/css/contact.css', array( 'scanshow-shared' ), '1.0.0' ); // Always enqueue config.js first wp_enqueue_script( 'scanshow-config-js', plugin_dir_url( __FILE__ ) . 'admin/js/config.js', array(), '1.0.0', true ); // Enqueue all page-specific scripts (they'll be loaded by router) wp_enqueue_script( 'scanshow-landing-js', plugin_dir_url( __FILE__ ) . 'admin/js/landing.js', array( 'jquery', 'scanshow-config-js' ), '1.0.0', true ); wp_enqueue_script( 'scanshow-settings-js', plugin_dir_url( __FILE__ ) . 'admin/js/settings.js', array( 'jquery', 'scanshow-config-js' ), '1.0.0', true ); wp_enqueue_script( 'scanshow-dashboard-js', plugin_dir_url( __FILE__ ) . 'admin/js/dashboard.js', array( 'jquery', 'scanshow-config-js' ), '1.0.0', true ); wp_enqueue_script( 'scanshow-contact-js', plugin_dir_url( __FILE__ ) . 'admin/js/contact.js', array( 'jquery', 'scanshow-config-js' ), '1.0.0', true ); // Enqueue router script last wp_enqueue_script( 'scanshow-router-js', plugin_dir_url( __FILE__ ) . 'admin/js/router.js', array( 'jquery', 'scanshow-config-js', 'scanshow-landing-js', 'scanshow-settings-js', 'scanshow-dashboard-js', 'scanshow-contact-js' ), '1.0.0', true ); // Detect theme and template information $theme_slug = get_stylesheet(); // Current theme directory name $template_slug = $this->detect_template_slug(); $available_templates = $this->get_single_templates(); // Construct single product editor URL $single_product_editor_url = admin_url( sprintf( 'site-editor.php?p=%s&canvas=edit', rawurlencode( "/wp_template/{$theme_slug}//{$template_slug}" ) ) ); wp_localize_script( 'scanshow-router-js', 'scanshowAdmin', array( 'restUrl' => rest_url( '3d-scan-and-show/v1/' ), 'nonce' => wp_create_nonce( 'wp_rest' ), 'ajaxUrl' => admin_url( 'admin-ajax.php' ), 'adminUrl' => admin_url(), 'themeName' => get_template(), 'themeSlug' => $theme_slug, 'templateSlug' => $template_slug, 'availableTemplates' => $available_templates, 'singleProductEditorUrl' => $single_product_editor_url, 'frontendUrl' => Scan_Show_API::get_frontend_url(), 'backendUrl' => Scan_Show_API::get_backend_url(), ) ); } /** * Load and render a PHP template file * Uses include for proper escaping - templates handle their own output * * @param string $template_name Template filename (without .php extension). */ private function render_template( $template_name ) { $file_path = plugin_dir_path( __FILE__ ) . 'admin/views/' . $template_name . '.php'; if ( file_exists( $file_path ) ) { include $file_path; } else { echo '

' . esc_html__( 'Error', '3d-scan-and-show' ) . '

' . esc_html__( 'View file not found:', '3d-scan-and-show' ) . ' ' . esc_html( $template_name ) . '

'; } } public function render_main_page() { // Redirect submenu pages to main page with hash routing. // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Reading admin page context, no data modification. $page = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : '3d-scan-and-show'; $route_map = array( '3d-scan-and-show-settings' => '/settings', '3d-scan-and-show-dashboard' => '/dashboard', '3d-scan-and-show-contact' => '/contact', ); // If accessed via submenu, redirect to hash URL if ( isset( $route_map[ $page ] ) ) { $redirect_url = admin_url( 'admin.php?page=3d-scan-and-show#' . $route_map[ $page ] ); wp_safe_redirect( $redirect_url ); exit; } ?>

3D Scan & Show: Product Viewer

Quick Start Settings Dashboard Contact Us
'POST', 'callback' => function( $request ) { $email = $request->get_param( 'email' ); $password = $request->get_param( 'password' ); if ( ! $email || ! $password ) { return new WP_Error( 'missing_fields', 'Email and password are required', array( 'status' => 400 ) ); } try { $result = Scan_Show_API::login( $email, $password ); // Handle shop already connected error - return full data for UI to handle if ( ! $result['success'] && isset( $result['code'] ) && $result['code'] === 'SHOP_ALREADY_CONNECTED' ) { return new WP_REST_Response( $result, 409 ); } return $result; } catch ( Exception $e ) { return new WP_Error( 'login_failed', $e->getMessage(), array( 'status' => 401 ) ); } }, 'permission_callback' => '__return_true', ) ); // Connection status endpoint register_rest_route( '3d-scan-and-show/v1', '/connection', array( 'methods' => 'GET', 'callback' => array( 'Scan_Show_API', 'get_connection_status' ), 'permission_callback' => '__return_true', ) ); register_rest_route( '3d-scan-and-show/v1', '/quota', array( 'methods' => 'GET', 'callback' => array( 'Scan_Show_API', 'get_quota' ), 'permission_callback' => '__return_true', ) ); register_rest_route( '3d-scan-and-show/v1', '/products', array( 'methods' => 'GET', 'callback' => array( 'Scan_Show_API', 'get_woocommerce_products' ), 'permission_callback' => '__return_true', ) ); register_rest_route( '3d-scan-and-show/v1', '/categories', array( 'methods' => 'GET', 'callback' => array( 'Scan_Show_API', 'get_woocommerce_categories' ), 'permission_callback' => '__return_true', ) ); // Get product link URL for popup window register_rest_route( '3d-scan-and-show/v1', '/product-link-url', array( 'methods' => 'GET', 'callback' => function( $request ) { $product_id = $request->get_param( 'product_id' ); if ( ! $product_id ) { return new WP_Error( 'missing_product_id', 'Product ID is required', array( 'status' => 400 ) ); } // Get shop_id from database $shop_id = Scan_Show_Database::get_shop_id(); if ( ! $shop_id ) { return new WP_Error( 'not_connected', 'Not connected to STW Platform', array( 'status' => 401 ) ); } // Construct dashboard URL: wordpress/[shopId]/dashboard/[product_id] $frontend_url = Scan_Show_API::get_frontend_url(); $link_url = rtrim( $frontend_url, '/' ) . '/wordpress/' . $shop_id . '/' . $product_id . '/dashboard'; return array( 'success' => true, 'url' => $link_url, ); }, 'permission_callback' => '__return_true', ) ); // Get linked items from backend API register_rest_route( '3d-scan-and-show/v1', '/linked-items', array( 'methods' => 'GET', 'callback' => array( 'Scan_Show_API', 'get_linked_items' ), 'permission_callback' => '__return_true', ) ); // Get iframe URL for displaying STW 3D model register_rest_route( '3d-scan-and-show/v1', '/iframe-url', array( 'methods' => 'GET', 'callback' => function( $request ) { $product_id = $request->get_param( 'product_id' ); if ( ! $product_id ) { return new WP_Error( 'missing_product_id', 'Product ID is required', array( 'status' => 400 ) ); } $iframe_url = Scan_Show_API::get_iframe_url( $product_id ); if ( ! $iframe_url ) { return array( 'success' => false, 'iframe_url' => '', 'message' => 'Product is not linked to any STW item', ); } return array( 'success' => true, 'iframe_url' => $iframe_url, ); }, 'permission_callback' => '__return_true', ) ); // Get all items (not just embed-enabled) register_rest_route( '3d-scan-and-show/v1', '/all-items', array( 'methods' => 'GET', 'callback' => function( $request ) { $items = Scan_Show_API::get_all_items(); if ( isset( $items['error'] ) ) { return new WP_Error( 'fetch_failed', $items['error'], array( 'status' => 500 ) ); } return array( 'success' => true, 'items' => $items, ); }, 'permission_callback' => '__return_true', ) ); // Get rooms register_rest_route( '3d-scan-and-show/v1', '/rooms', array( 'methods' => 'GET', 'callback' => function( $request ) { $page = $request->get_param( 'page' ) ?: 1; $page_size = $request->get_param( 'pageSize' ) ?: 32; $rooms = Scan_Show_API::get_rooms( $page, $page_size ); if ( isset( $rooms['error'] ) ) { return new WP_Error( 'fetch_failed', $rooms['error'], array( 'status' => 500 ) ); } return array( 'success' => true, 'rooms' => $rooms, ); }, 'permission_callback' => '__return_true', ) ); // Enable embed for item register_rest_route( '3d-scan-and-show/v1', '/item/(?P[a-zA-Z0-9\-]+)/embed', array( 'methods' => 'POST', 'callback' => function( $request ) { $item_id = $request->get_param( 'id' ); $result = Scan_Show_API::enable_item_embed( $item_id ); if ( ! $result['success'] ) { $status = isset( $result['quota_exceeded'] ) && $result['quota_exceeded'] ? 403 : 500; return new WP_Error( 'embed_failed', $result['error'], array( 'status' => $status ) ); } return array( 'success' => true, 'data' => $result['data'], ); }, 'permission_callback' => function() { return current_user_can( 'edit_posts' ); }, ) ); // Enable embed for room register_rest_route( '3d-scan-and-show/v1', '/room/(?P[a-zA-Z0-9\-]+)/embed', array( 'methods' => 'POST', 'callback' => function( $request ) { $room_id = $request->get_param( 'id' ); $result = Scan_Show_API::enable_room_embed( $room_id ); if ( ! $result['success'] ) { $status = isset( $result['quota_exceeded'] ) && $result['quota_exceeded'] ? 403 : 500; return new WP_Error( 'embed_failed', $result['error'], array( 'status' => $status ) ); } return array( 'success' => true, 'data' => $result['data'], ); }, 'permission_callback' => function() { return current_user_can( 'edit_posts' ); }, ) ); // Link a page to an item or room register_rest_route( '3d-scan-and-show/v1', '/page-item-link', array( 'methods' => 'POST', 'callback' => function( $request ) { $page_id = $request->get_param( 'page_id' ); $entity_id = $request->get_param( 'entity_id' ); $entity_type = $request->get_param( 'entity_type' ) ?: 'item'; // Support legacy parameter name if ( ! $entity_id ) { $entity_id = $request->get_param( 'item_id' ); } if ( ! $page_id || ! $entity_id ) { return new WP_Error( 'missing_params', 'Page ID and Entity ID are required', array( 'status' => 400 ) ); } $result = Scan_Show_Database::link_page_to_item( $page_id, $entity_id, $entity_type ); if ( $result === false ) { return new WP_Error( 'link_failed', 'Failed to link page to entity', array( 'status' => 500 ) ); } return array( 'success' => true, 'message' => 'Page linked successfully', ); }, 'permission_callback' => function() { return current_user_can( 'edit_posts' ); }, ) ); // Get linked item/room for a page register_rest_route( '3d-scan-and-show/v1', '/page-item-link/(?P\d+)', array( 'methods' => 'GET', 'callback' => function( $request ) { $page_id = $request->get_param( 'page_id' ); $link_data = Scan_Show_Database::get_page_link_data( $page_id ); if ( $link_data ) { return array( 'success' => true, 'entity_id' => $link_data['entity_id'], 'entity_type' => $link_data['entity_type'], // Legacy support 'item_id' => $link_data['entity_id'], ); } return array( 'success' => true, 'entity_id' => null, 'entity_type' => null, 'item_id' => null, ); }, 'permission_callback' => '__return_true', ) ); // Unlink a page from its item register_rest_route( '3d-scan-and-show/v1', '/page-item-link/(?P\d+)', array( 'methods' => 'DELETE', 'callback' => function( $request ) { $page_id = $request->get_param( 'page_id' ); $result = Scan_Show_Database::unlink_page_from_item( $page_id ); if ( ! $result ) { return new WP_Error( 'unlink_failed', 'Failed to unlink page from item', array( 'status' => 500 ) ); } return array( 'success' => true, 'message' => 'Page unlinked successfully', ); }, 'permission_callback' => function() { return current_user_can( 'edit_posts' ); }, ) ); // Get iframe URL for a page register_rest_route( '3d-scan-and-show/v1', '/page-iframe-url', array( 'methods' => 'GET', 'callback' => function( $request ) { $page_id = $request->get_param( 'page_id' ); if ( ! $page_id ) { return new WP_Error( 'missing_page_id', 'Page ID is required', array( 'status' => 400 ) ); } $iframe_url = Scan_Show_API::get_page_iframe_url( $page_id ); if ( ! $iframe_url ) { return array( 'success' => false, 'iframe_url' => '', 'message' => 'Page is not linked to any STW item', ); } return array( 'success' => true, 'iframe_url' => $iframe_url, ); }, 'permission_callback' => '__return_true', ) ); register_rest_route( '3d-scan-and-show/v1', '/remove-connection', array( 'methods' => 'POST', 'callback' => function( $request ) { $result = Scan_Show_API::remove_shop_connection(); if ( ! $result['success'] ) { return new WP_Error( 'connection_removal_failed', $result['error'], array( 'status' => 500 ) ); } return array( 'success' => true, 'message' => 'Shop connection removed successfully', ); }, 'permission_callback' => function() { return current_user_can( 'manage_options' ); }, ) ); } } new Scan_Show_WordPress_Plugin();