PluginProbe
Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant / 2.3.2
Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant v2.3.2
2.3.2 2.3.1 2.3.0 2.2.8 2.2.7 trunk 1.10.0 1.10.1 1.10.2 1.10.3 1.10.4 1.10.5 1.11.0 1.11.1 1.11.2 1.6 1.7 1.8 1.8.1 1.8.2 1.8.3 1.9.0 1.9.1 1.9.10 1.9.11 All 60 releases
← All changes | inc/modules/quick-view/class-quick-view.php +142 -187 1.10.52.3.2 View file →
@@ -22,8 +22,14 @@
22 22 */
23 23 const MODULE_ID = 'quick-view';
24 24
25 25 /**
26 + * Module templates path.
27 + *
28 + */
29 + const MODULE_TEMPLATES = 'modules/' . self::MODULE_ID;
30 +
31 + /**
26 32 * Is module preview.
27 33 *
28 34 */
29 35 public static $is_module_preview = false;
@@ -37,8 +43,17 @@
37 43
38 44 private static $instance = null;
39 45
40 46 /**
47 + * Initialize the module's option group definitions.
48 + *
49 + * @return array<int, array<string, mixed>> Array of option group arrays.
50 + */
51 + protected function init_option_groups() {
52 + return require MERCHANT_DIR . 'inc/modules/' . self::MODULE_ID . '/admin/options.php';
53 + }
54 +
55 + /**
41 56 * Constructor.
42 57 *
43 58 */
44 59 public function __construct() {
@@ -65,8 +80,11 @@
65 80 'button-position-left' => 50,
66 81 'mobile_position' => false,
67 82 'button-position-top-mobile' => 50,
68 83 'button-position-left-mobile' => 50,
84 + 'modal_layout' => 'modal',
85 + 'show_navigation_arrows' => 0,
86 + 'sync_url_hash' => 0,
69 87 'zoom_effect' => 1,
70 88 'show_quantity' => 1,
71 89 'place_product_description' => 'top',
72 90 'description_style' => 'short',
@@ -76,19 +94,22 @@
76 94 'suggested_products_module' => 'bulk_discounts',
77 95 'suggested_products_placement' => 'after_add_to_cart',
78 96 );
79 97
80 - // Mount preview url.
81 - $preview_url = site_url( '/' );
82 -
83 - if ( function_exists( 'wc_get_page_id' ) ) {
84 - $preview_url = get_permalink( wc_get_page_id( 'shop' ) );
85 - }
86 -
87 98 // Module data.
88 99 $this->module_data = Merchant_Admin_Modules::$modules_data[ self::MODULE_ID ];
89 - $this->module_data[ 'preview_url' ] = $preview_url;
90 100
101 + // AI prompt examples.
102 + $this->ai_examples = array(
103 + 'blurb' => esc_html__( 'See what AI can do for your Quick View popup, from restyling the button to rearranging what shows inside the modal.', 'merchant' ),
104 + 'prompts' => array(
105 + esc_html__( "Explore the abilities WPVibe gives you access to, then set the Quick View button to show an icon and text, label it 'View Details', and use the cart icon", 'merchant' ),
106 + esc_html__( 'Check what abilities you have available through WPVibe, then overlay the Quick View button (labeled View Details) on the product image, positioned 20% from the top and 80% from the left', 'merchant' ),
107 + esc_html__( 'Look at your available WPVibe abilities, then widen the quick view modal to 1200px, move the gallery thumbnails to the bottom, and show the full product description', 'merchant' ),
108 + esc_html__( 'See what abilities WPVibe exposes, then enable ajax add to cart in the quick view popup and hide the quantity selector', 'merchant' ),
109 + ),
110 + );
111 +
91 112 // Module options path.
92 113 $this->module_options_path = MERCHANT_DIR . 'inc/modules/' . self::MODULE_ID . '/admin/options.php';
93 114
94 115 // Is module preview page.
@@ -177,8 +198,12 @@
177 198 // Show Suggested Module
178 199 $suggested_placement = Merchant_Admin_Options::get( self::MODULE_ID, 'suggested_products_placement', 'after_add_to_cart' );
179 200 add_action( 'merchant_quick_view_' . $suggested_placement, array( $this, 'render_suggested_module_content' ), 10, 2 );
180 201
202 + // Let other modules (e.g. Pro asset managers) ask which suggested-products
203 + // module is active without depending on Quick View's own option keys.
204 + add_filter( 'merchant_quick_view_suggested_products_module', array( $this, 'get_active_suggested_products_module' ) );
205 +
181 206 // Show Buy Now Module
182 207 add_action( 'woocommerce_after_add_to_cart_button', array( $this, 'render_buy_now_button' ) );
183 208
184 209 // Custom CSS.
@@ -186,11 +211,23 @@
186 211
187 212 // Modal content ajax callback.
188 213 add_action( 'wp_ajax_merchant_quick_view_content', array( $this, 'modal_content_ajax_callback' ) );
189 214 add_action( 'wp_ajax_nopriv_merchant_quick_view_content', array( $this, 'modal_content_ajax_callback' ) );
215 +
216 + // Initialize AJAX add to cart handler (separate class).
217 + $this->init_ajax_add_to_cart();
190 218 }
191 219
192 220 /**
221 + * Initialize AJAX add to cart functionality.
222 + *
223 + * @return void
224 + */
225 + private function init_ajax_add_to_cart() {
226 + Merchant_Quick_View_Ajax_Add_To_Cart::get_instance()->init();
227 + }
228 +
229 + /**
193 230 * Singleton
194 231 *
195 232 * @return self|null
196 233 */
@@ -290,10 +327,15 @@
290 327 *
291 328 * @return void
292 329 */
293 330 public function enqueue_scripts() {
294 - wp_enqueue_script( 'zoom' );
295 - wp_enqueue_script( 'flexslider' );
331 + if ( defined( 'WC_VERSION' ) && version_compare( WC_VERSION, '10.3', '>=' ) ) {
332 + wp_enqueue_script( 'wc-zoom' );
333 + wp_enqueue_script( 'wc-flexslider' );
334 + } else {
335 + wp_enqueue_script( 'zoom' );
336 + wp_enqueue_script( 'flexslider' );
337 + }
296 338 wp_enqueue_script( 'wc-single-product' );
297 339 wp_enqueue_script( 'wc-add-to-cart-variation' );
298 340
299 341 // Register and enqueue the main module script.
@@ -308,10 +350,12 @@
308 350 */
309 351 public function localize_script( $setting ) {
310 352 $module_settings = $this->get_module_settings();
311 353
312 - $setting[ 'quick_view' ] = true;
313 - $setting[ 'quick_view_zoom' ] = $module_settings[ 'zoom_effect' ];
354 + $setting[ 'quick_view' ] = true;
355 + $setting[ 'quick_view_zoom' ] = $module_settings[ 'zoom_effect' ];
356 + $setting[ 'ajax_add_to_cart' ] = ! empty( $module_settings[ 'ajax_add_to_cart' ] );
357 + $setting[ 'sync_url_hash' ] = ! empty( $module_settings[ 'sync_url_hash' ] );
314 358
315 359 return $setting;
316 360 }
317 361
@@ -428,11 +472,10 @@
428 472 }
429 473
430 474 /**
431 475 * Modal content ajax callback.
432 - * TODO: Render the output through templates files.
433 - *
434 - * @return void
476 + *
477 + * @return void
435 478 */
436 479 public function modal_content_ajax_callback() {
437 480 check_ajax_referer( 'merchant-nonce', 'nonce' );
438 481
@@ -443,9 +486,9 @@
443 486 $args = array(
444 487 'product_id' => absint( $_POST['product_id'] ),
445 488 );
446 489
447 - global $product;
490 + global $product, $post;
448 491
449 492 $settings = $this->get_module_settings();
450 493 $product = wc_get_product( $args['product_id'] );
451 494
@@ -452,174 +495,37 @@
452 495 if ( is_wp_error( $product ) || empty( $product ) ) {
453 496 wp_send_json_error();
454 497 }
455 498
456 - $product_id = $product->get_id();
457 - $hide_quantity = ( empty( $settings[ 'show_quantity' ] ) ) ? 'merchant-hide-quantity' : '';
499 + // Mirror the real single-product page's main-query post so functions like
500 + // `get_the_ID()` (used by other modules hooking into `merchant_quick_view_product_summary`,
501 + // e.g. Payment Logos' exclusion checks) resolve correctly during this AJAX request.
502 + $post = get_post( $args['product_id'] ); // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
458 503
459 - ob_start();
460 - ?>
461 - <div id="product-<?php echo absint( $product_id ); ?>" <?php wc_product_class( '', $product ); ?>>
462 - <div class="merchant-quick-view-row">
463 - <div class="merchant-quick-view-column">
464 - <div class="merchant-quick-view-product-gallery">
465 - <?php woocommerce_show_product_images(); ?>
466 - </div>
467 - </div>
504 + $content = merchant_get_template_part(
505 + self::MODULE_TEMPLATES,
506 + 'content',
507 + array(
508 + 'product' => $product,
509 + 'settings' => $settings,
510 + ),
511 + true
512 + );
468 513
469 - <div class="merchant-quick-view-column">
470 - <div class="merchant-quick-view-summary product-gallery-summary">
471 - <div class="merchant-quick-view-product-title">
472 - <h2 class="product_title entry-title"><?php echo esc_html( $product->get_title() ); ?></h2>
473 - </div>
474 -
475 - <?php if ( 0 !== $product->get_average_rating() ) : ?>
476 - <div class="merchant-quick-view-product-rating">
477 - <div class="woocommerce-product-rating">
478 - <?php echo wp_kses( wc_get_rating_html( $product->get_average_rating() ), merchant_kses_allowed_tags() ); ?>
479 - </div>
480 - </div>
481 - <?php endif; ?>
482 -
483 - <div class="merchant-quick-view-product-price">
484 - <div class="price"><?php echo wp_kses( $product->get_price_html(), merchant_kses_allowed_tags() ); ?></div>
485 - </div>
486 -
487 - <?php if ( 'top' === $settings[ 'place_product_description' ] ) : ?>
488 - <?php
489 - /**
490 - * Hook 'merchant_quick_view_before_product_description'
491 - *
492 - * @since 1.9.14
493 - */
494 - do_action( 'merchant_quick_view_before_product_description' );
495 -
496 - $description = $settings[ 'description_style' ] === 'full' ? $product->get_description() : $product->get_short_description();
497 -
498 - /**
499 - * `merchant_quick_view_description`
500 - *
501 - * @since 1.9.16
502 - */
503 - $description = apply_filters( 'merchant_quick_view_description', $description );
504 - ?>
505 - <div class="merchant-quick-view-product-excerpt">
506 - <?php echo wp_kses_post( $description ); ?>
507 - </div>
508 -
509 - <?php
510 - /**
511 - * Hook 'merchant_quick_view_before_product_description'
512 - *
513 - * @since 1.9.14
514 - */
515 - do_action( 'merchant_quick_view_after_product_description' );
516 - ?>
517 - <?php endif; ?>
518 -
519 - <?php
520 - /**
521 - * Hook 'merchant_quick_view_before_add_to_cart'
522 - *
523 - * @since 1.9.14
524 - */
525 - do_action( 'merchant_quick_view_before_add_to_cart', $product, $settings );
526 - ?>
527 - <div class="merchant-quick-view-product-add-to-cart <?php echo esc_attr( $hide_quantity ); ?>">
528 - <?php woocommerce_template_single_add_to_cart(); ?>
529 - </div>
530 -
531 - <?php
532 - /**
533 - * Hook 'merchant_quick_view_after_add_to_cart'
534 - *
535 - * @since 1.9.14
536 - */
537 - do_action( 'merchant_quick_view_after_add_to_cart', $product, $settings );
538 - ?>
539 -
540 - <?php if ( 'bottom' === $settings[ 'place_product_description' ] ) : ?>
541 - <?php
542 - /**
543 - * Hook 'merchant_quick_view_before_product_description'
544 - *
545 - * @since 1.9.14
546 - */
547 - do_action( 'merchant_quick_view_before_product_description' );
548 -
549 - $description = $settings[ 'description_style' ] === 'full' ? $product->get_description() : $product->get_short_description();
550 -
551 - /**
552 - * `merchant_quick_view_description`
553 - *
554 - * @since 1.9.16
555 - */
556 - $description = apply_filters( 'merchant_quick_view_description', $description );
557 - ?>
558 - <div class="merchant-quick-view-product-excerpt">
559 - <?php echo wp_kses_post( $description ); ?>
560 - </div>
561 -
562 - <?php
563 - /**
564 - * Hook 'merchant_quick_view_after_product_description'
565 - *
566 - * @since 1.9.14
567 - */
568 - do_action( 'merchant_quick_view_after_product_description' );
569 - ?>
570 - <?php endif; ?>
571 - <div class="merchant-quick-view-product-meta">
572 - <?php woocommerce_template_single_meta(); ?>
573 - </div>
574 -
575 - <?php
576 - /**
577 - * Hook 'merchant_quick_view_after_product_excerpt'
578 - *
579 - * @since 1.0.0
580 - */
581 - do_action( 'merchant_quick_view_after_product_meta' );
582 - ?>
583 - </div>
584 - </div>
585 - </div>
586 - </div>
587 - <?php
588 - $content = ob_get_contents();
589 -
590 - ob_get_clean();
591 -
592 514 wp_send_json_success( $content );
593 515 }
594 516
517 +
595 518 /**
596 519 * Modal output.
597 - * TODO: Render the output through templates files.
598 - *
520 + *
599 521 * @return void
600 522 */
601 523 public function modal_output() {
602 524 $settings = $this->get_module_settings();
603 - ?>
604 - <div class="single-product merchant-quick-view-modal merchant-quick-view-<?php echo esc_attr( $settings[ 'place_product_image' ] ); ?>">
605 - <div class="merchant-quick-view-overlay"></div>
606 - <div class="merchant-quick-view-loader">
607 - <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
608 - <path opacity="0.4" d="M478.71 364.58zm-22 6.11l-27.83-15.9a15.92 15.92 0 0 1-6.94-19.2A184 184 0 1 1 256 72c5.89 0 11.71.29 17.46.83-.74-.07-1.48-.15-2.23-.21-8.49-.69-15.23-7.31-15.23-15.83v-32a16 16 0 0 1 15.34-16C266.24 8.46 261.18 8 256 8 119 8 8 119 8 256s111 248 248 248c98 0 182.42-56.95 222.71-139.42-4.13 7.86-14.23 10.55-22 6.11z" />
609 - <path d="M271.23 72.62c-8.49-.69-15.23-7.31-15.23-15.83V24.73c0-9.11 7.67-16.78 16.77-16.17C401.92 17.18 504 124.67 504 256a246 246 0 0 1-25 108.24c-4 8.17-14.37 11-22.26 6.45l-27.84-15.9c-7.41-4.23-9.83-13.35-6.2-21.07A182.53 182.53 0 0 0 440 256c0-96.49-74.27-175.63-168.77-183.38z" />
610 - </svg>
611 - </div>
612 - <div class="merchant-quick-view-inner">
613 - <a href="#" class="merchant-quick-view-close-button" title="<?php echo esc_attr__( 'Close quick view modal', 'merchant' ); ?>">
614 - <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512">
615 - <path d="M193.94 256L296.5 153.44l21.15-21.15c3.12-3.12 3.12-8.19 0-11.31l-22.63-22.63c-3.12-3.12-8.19-3.12-11.31 0L160 222.06 36.29 98.34c-3.12-3.12-8.19-3.12-11.31 0L2.34 120.97c-3.12 3.12-3.12 8.19 0 11.31L126.06 256 2.34 379.71c-3.12 3.12-3.12 8.19 0 11.31l22.63 22.63c3.12 3.12 8.19 3.12 11.31 0L160 289.94 262.56 392.5l21.15 21.15c3.12 3.12 8.19 3.12 11.31 0l22.63-22.63c3.12-3.12 3.12-8.19 0-11.31L193.94 256z"/>
616 - </svg>
617 - </a>
618 - <div class="merchant-quick-view-content"></div>
619 - </div>
620 - </div>
621 - <?php
525 + $template = ( 'side-panel' === $settings[ 'modal_layout' ] ) ? 'side-panel' : 'modal';
526 +
527 + merchant_get_template_part( self::MODULE_TEMPLATES, $template, array( 'settings' => $settings ) );
622 528 }
623 529
624 530 /**
625 531 * Quick view button.
@@ -678,8 +584,23 @@
678 584 }
679 585
680 586 global $product;
681 587
588 + /**
589 + * Filters whether the Buy Now button should be excluded for a specific product in Quick View.
590 + *
591 + * This filter allows modules (like Buy Now) to control the visibility of the Buy Now button
592 + * in the Quick View modal based on their own exclusion rules.
593 + *
594 + * @param bool $is_excluded Whether the product is excluded. Default false.
595 + * @param WC_Product $product The product object.
596 + *
597 + * @since 2.2.4
598 + */
599 + if ( apply_filters( 'merchant_buy_now_is_excluded', false, $product ) ) {
600 + return;
601 + }
602 +
682 603 $text = Merchant_Admin_Options::get( Merchant_Buy_Now::MODULE_ID, 'button-text', esc_html__( 'Buy Now', 'merchant' ) );
683 604
684 605 $_wrapper_classes = array();
685 606 $_wrapper_classes[] = $product->get_type() === 'variable' ? 'disabled' : '';
@@ -728,8 +649,27 @@
728 649 }
729 650 }
730 651
731 652 /**
653 + * Gets the currently configured suggested-products module.
654 + *
655 + * Filter callback for `merchant_quick_view_suggested_products_module`, so
656 + * other modules can check which module is active without reading Quick
657 + * View's option keys directly.
658 + *
659 + * @param string|false $default_value Default value passed through the filter.
660 + *
661 + * @return string|false The active module slug, or false if suggested products are disabled.
662 + */
663 + public function get_active_suggested_products_module( $default_value = false ) {
664 + if ( ! Merchant_Admin_Options::get( self::MODULE_ID, 'show_suggested_products', true ) ) {
665 + return false;
666 + }
667 +
668 + return Merchant_Admin_Options::get( self::MODULE_ID, 'suggested_products_module', 'bulk_discounts' );
669 + }
670 +
671 + /**
732 672 * Prints the bulk discounts for the specified product.
733 673 *
734 674 * @param $product
735 675 *
@@ -771,10 +711,16 @@
771 711 if ( ! merchant_is_pro_active() || ! Merchant_Modules::is_module_active( Merchant_Buy_X_Get_Y::MODULE_ID ) ) {
772 712 return;
773 713 }
774 714
715 + $eligibility = Merchant_Pro_BXGY_Service_Provider::eligibility();
716 +
717 + if ( ! $eligibility ) {
718 + return;
719 + }
720 +
775 721 $product_id = $product->get_id();
776 - $offers = Merchant_Pro_Buy_X_Get_Y::availabe_offers( $product_id );
722 + $offers = $eligibility->get_eligible_for_product( $product_id );
777 723
778 724 if ( ! empty( $offers ) ) {
779 725 merchant_get_template_part(
780 726 Merchant_Buy_X_Get_Y::MODULE_TEMPLATES,
@@ -779,12 +725,13 @@
779 725 merchant_get_template_part(
780 726 Merchant_Buy_X_Get_Y::MODULE_TEMPLATES,
781 727 'single-product',
782 728 array(
783 - 'offers' => $offers,
784 - 'nonce' => wp_create_nonce( 'merchant_bogo_add_to_cart' ),
785 - 'settings' => Merchant_Admin_Options::get_all( Merchant_Buy_X_Get_Y::MODULE_ID ),
786 - 'product' => $product_id,
729 + 'offers' => $offers,
730 + 'nonce' => wp_create_nonce( 'merchant_bogo_add_to_cart' ),
731 + 'settings' => Merchant_Admin_Options::get_all( Merchant_Buy_X_Get_Y::MODULE_ID ),
732 + 'product' => $product_id,
733 + 'module_id' => Merchant_Buy_X_Get_Y::MODULE_ID,
787 734 )
788 735 );
789 736 }
790 737 }
@@ -800,23 +747,24 @@
800 747 if ( ! merchant_is_pro_active() || ! Merchant_Modules::is_module_active( Merchant_Frequently_Bought_Together::MODULE_ID ) ) {
801 748 return;
802 749 }
803 750
804 - $post_id = $product->get_id();
805 - $bundle_data = Merchant_Pro_Frequently_Bought_Together::availabe_offers( $post_id, Merchant_Pro_Frequently_Bought_Together::bundles() );
751 + $post_id = $product->get_id();
752 + $repository = new Merchant_Pro_FBT_Offer_Repository( Merchant_Frequently_Bought_Together::MODULE_ID );
753 + $price_calc = new Merchant_Pro_FBT_Price_Calculator( Merchant_Frequently_Bought_Together::MODULE_ID );
754 + $bundle_mapper = new Merchant_Pro_FBT_Bundle_Mapper( $price_calc );
755 + $offer_resolver = new Merchant_Pro_FBT_Offer_Resolver( $repository, $bundle_mapper );
756 + $bundles = $offer_resolver->available_offers( $post_id );
806 757
807 - if ( ! empty( $bundle_data ) ) {
808 - $bundles[ $post_id ] = Merchant_Pro_Frequently_Bought_Together::map_bundles( $bundle_data );
809 - }
810 -
811 758 if ( ! empty( $bundles ) ) {
812 759 merchant_get_template_part(
813 760 Merchant_Frequently_Bought_Together::MODULE_TEMPLATES_PATH,
814 - 'single-product',
761 + 'single-product-v3',
815 762 array(
816 - 'bundles' => $bundles,
817 - 'nonce' => wp_create_nonce( Merchant_Pro_Frequently_Bought_Together::AJAX_NONCE_ACTION ),
818 - 'settings' => Merchant_Admin_Options::get_all( Merchant_Frequently_Bought_Together::MODULE_ID ),
763 + 'bundle' => reset( $bundles ),
764 + 'nonce' => wp_create_nonce( Merchant_Pro_Frequently_Bought_Together::AJAX_NONCE_ACTION ),
765 + 'settings' => Merchant_Admin_Options::get_all( Merchant_Frequently_Bought_Together::MODULE_ID ),
766 + 'module_id' => Merchant_Frequently_Bought_Together::MODULE_ID,
819 767 )
820 768 );
821 769 }
822 770 }
@@ -870,8 +818,11 @@
870 818
871 819 // Modal Height.
872 820 $css .= Merchant_Custom_CSS::get_variable_css( 'quick-view', 'modal_height', 500, '.merchant-quick-view-modal', '--mrc-qv-modal-height', 'px' );
873 821
822 + // Side Panel Width.
823 + $css .= Merchant_Custom_CSS::get_variable_css( 'quick-view', 'side_panel_width', 420, '.merchant-quick-view-modal', '--mrc-qv-panel-width', 'px' );
824 +
874 825 // Sale Price Color.
875 826 $css .= Merchant_Custom_CSS::get_variable_css( 'quick-view', 'sale-price-color', '#212121', '.merchant-quick-view-modal', '--mrc-qv-modal-sale-price-color' );
876 827
877 828 // Regular Price Color.
@@ -928,6 +879,10 @@
928 879 }
929 880
930 881 // Initialize the module.
931 882 add_action( 'init', function() {
932 - Merchant_Quick_View::get_instance();
883 + // Load AJAX add to cart class file.
884 + require_once MERCHANT_DIR . 'inc/modules/quick-view/class-quick-view-ajax-add-to-cart.php';
885 +
886 + // Create and initialize the module.
887 + Merchant_Modules::create_module( Merchant_Quick_View::get_instance() );
933 888 } );