get_admin_assets(), 'enqueue' ) ); add_filter( 'woocommerce_product_data_tabs', array( $container->get_admin_panel(), 'add_tab' ) ); add_action( 'woocommerce_product_data_panels', array( $container->get_admin_panel(), 'render_panel' ) ); add_action( 'woocommerce_product_after_variable_attributes', array( $container->get_admin_panel(), 'render_variation_fields' ), 10, 3 ); add_action( 'woocommerce_process_product_meta', array( $container->get_admin_saver(), 'save_product' ) ); add_action( 'woocommerce_save_product_variation', array( $container->get_admin_saver(), 'save_variation' ), 10, 2 ); add_action( 'before_delete_post', array( $container->get_admin_saver(), 'remember_deleted_product' ), 10, 2 ); add_action( 'deleted_post', array( $container->get_admin_saver(), 'forget_deleted_product' ), 10, 2 ); add_action( 'admin_init', array( __CLASS__, 'dismiss_model_offer' ) ); add_action( 'admin_notices', array( __CLASS__, 'model_offer_notice' ) ); add_action( 'admin_notices', array( __CLASS__, 'storage_notice' ) ); add_action( 'wp_ajax_try3d_upload', array( $container->get_admin_ajax(), 'handle_upload' ) ); add_action( 'wp_ajax_try3d_preview', array( $container->get_admin_ajax(), 'handle_preview' ) ); } /** * @param array> $tabs Tabs. * @return array> */ public static function tab( $tabs ) { return Try3D_Container::get_instance()->get_admin_panel()->add_tab( $tabs ); } /** * @return void */ public static function panel() { Try3D_Container::get_instance()->get_admin_panel()->render_panel(); } /** * @param int $loop Variation index. * @param array $variation_data Variation meta. * @param WP_Post $variation Variation post. * @return void */ public static function variation_fields( $loop, $variation_data, $variation ) { Try3D_Container::get_instance()->get_admin_panel()->render_variation_fields( $loop, $variation_data, $variation ); } /** * @param string $token_name Input name. * @param string $filename_name Input name. * @param string $token Stored token. * @param string $filename Stored label. * @return void */ public static function file_field( $token_name, $filename_name, $token, $filename ) { Try3D_Container::get_instance()->get_admin_panel()->render_file_field( $token_name, $filename_name, $token, $filename ); } /** * @param int $post_id Product ID. * @return void */ public static function save_product( $post_id ) { Try3D_Container::get_instance()->get_admin_saver()->save_product( $post_id ); } /** * @param int $variation_id Variation ID. * @param int $loop Posted index. * @return void */ public static function save_variation( $variation_id, $loop ) { Try3D_Container::get_instance()->get_admin_saver()->save_variation( $variation_id, $loop ); } /** * @param string $hook Current admin page. * @return void */ public static function assets( $hook ) { Try3D_Container::get_instance()->get_admin_assets()->enqueue( $hook ); } /** * @return void */ public static function ajax_upload() { Try3D_Container::get_instance()->get_admin_ajax()->handle_upload(); } /** * @return void */ public static function preview_model() { Try3D_Container::get_instance()->get_admin_ajax()->handle_preview(); } /** * @param int $post_id Post ID. * @param WP_Post|null $post Post object. * @return void */ public static function forget_deleted_product( $post_id, $post = null ) { Try3D_Container::get_instance()->get_admin_saver()->forget_deleted_product( $post_id, $post ); } /** * Offer one product model to the shop owner, then stay hidden after they close it. * * @return void */ public static function model_offer_notice() { if ( ! current_user_can( 'edit_products' ) || get_user_meta( get_current_user_id(), 'try3d_hide_model_offer', true ) ) { return; } $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null; if ( ! $screen || ! in_array( $screen->id, array( 'dashboard', 'plugins', 'product', 'edit-product' ), true ) ) { return; } $dismiss = wp_nonce_url( add_query_arg( 'try3d_dismiss_offer', '1' ), 'try3d_dismiss_offer' ); echo '

'; echo esc_html__( 'No GLB file yet? Send the link to one product. The first model is free, and each model after that is $15.', 'try3d-viewer' ); echo ' ' . esc_html__( 'Request the first model', 'try3d-viewer' ) . ''; echo ' ยท ' . esc_html__( 'Dismiss', 'try3d-viewer' ) . ''; echo '

'; } /** * Remember that this user closed the model offer. * * @return void */ public static function dismiss_model_offer() { if ( empty( $_GET['try3d_dismiss_offer'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended return; } if ( ! current_user_can( 'edit_products' ) ) { return; } check_admin_referer( 'try3d_dismiss_offer' ); update_user_meta( get_current_user_id(), 'try3d_hide_model_offer', '1' ); wp_safe_redirect( remove_query_arg( array( 'try3d_dismiss_offer', '_wpnonce' ) ) ); exit; } /** * Ask for a server rule when model files remain inside the public web root. * * @return void */ public static function storage_notice() { if ( ! current_user_can( 'manage_options' ) ) { return; } $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null; if ( ! $screen || ! in_array( $screen->id, array( 'plugins', 'product', 'edit-product' ), true ) ) { return; } $software = isset( $_SERVER['SERVER_SOFTWARE'] ) ? strtolower( (string) $_SERVER['SERVER_SOFTWARE'] ) : ''; if ( false === strpos( $software, 'nginx' ) ) { return; } $storage = Try3D_Container::get_instance()->get_storage(); if ( ! $storage->is_inside_web_root() ) { return; } $dir = wp_normalize_path( $storage->get_dir() ); $root = wp_normalize_path( untrailingslashit( ABSPATH ) ); if ( '' === $dir || 0 !== strpos( $dir, $root ) ) { return; } $rel = '/' . ltrim( substr( $dir, strlen( $root ) ), '/' ); if ( '/' === $rel ) { return; } echo '

'; echo esc_html__( '3D models are still inside the web root, and Nginx ignores .htaccess files. Deny this path in the Nginx configuration so model files cannot be downloaded directly.', 'try3d-viewer' ); echo '

'; echo esc_html( 'location ^~ ' . trailingslashit( $rel ) . ' { deny all; }' ); echo '

'; } }