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 +464 -166 1.9.102.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;
@@ -28,8 +34,26 @@
28 34 */
29 35 public static $is_module_preview = false;
30 36
31 37 /**
38 + * Whether the module has a shortcode or not.
39 + *
40 + * @var bool
41 + */
42 + public $has_shortcode = true;
43 +
44 + private static $instance = null;
45 +
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 + /**
32 56 * Constructor.
33 57 *
34 58 */
35 59 public function __construct() {
@@ -47,32 +71,45 @@
47 71 $this->module_section = 'convert-more';
48 72
49 73 // Module default settings.
50 74 $this->module_default_settings = array(
51 - 'button_type' => 'text',
52 - 'button_text' => __( 'Quick View', 'merchant' ),
53 - 'button_icon' => 'eye',
54 - 'button_position' => 'overlay',
55 - 'button-position-top' => 50,
56 - 'button-position-left' => 50,
57 - 'zoom_effect' => 1,
58 - 'show_quantity' => 1,
59 - 'place_product_description' => 'top',
60 - 'description_style' => 'short',
61 - 'place_product_image' => 'thumbs-at-left',
75 + 'button_type' => 'text',
76 + 'button_text' => __( 'Quick View', 'merchant' ),
77 + 'button_icon' => 'eye',
78 + 'button_position' => 'overlay',
79 + 'button-position-top' => 50,
80 + 'button-position-left' => 50,
81 + 'mobile_position' => false,
82 + 'button-position-top-mobile' => 50,
83 + 'button-position-left-mobile' => 50,
84 + 'modal_layout' => 'modal',
85 + 'show_navigation_arrows' => 0,
86 + 'sync_url_hash' => 0,
87 + 'zoom_effect' => 1,
88 + 'show_quantity' => 1,
89 + 'place_product_description' => 'top',
90 + 'description_style' => 'short',
91 + 'place_product_image' => 'thumbs-at-left',
92 + 'show_buy_now_button' => false,
93 + 'show_suggested_products' => true,
94 + 'suggested_products_module' => 'bulk_discounts',
95 + 'suggested_products_placement' => 'after_add_to_cart',
62 96 );
63 97
64 - // Mount preview url.
65 - $preview_url = site_url( '/' );
66 -
67 - if ( function_exists( 'wc_get_page_id' ) ) {
68 - $preview_url = get_permalink( wc_get_page_id( 'shop' ) );
69 - }
70 -
71 98 // Module data.
72 99 $this->module_data = Merchant_Admin_Modules::$modules_data[ self::MODULE_ID ];
73 - $this->module_data[ 'preview_url' ] = $preview_url;
74 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 +
75 112 // Module options path.
76 113 $this->module_options_path = MERCHANT_DIR . 'inc/modules/' . self::MODULE_ID . '/admin/options.php';
77 114
78 115 // Is module preview page.
@@ -100,10 +137,10 @@
100 137 return;
101 138 }
102 139
103 140 // Return early if it's on admin but not in the respective module settings page.
104 - if ( is_admin() && ! parent::is_module_settings_page() && ! defined( 'DOING_AJAX' ) ) {
105 - return;
141 + if ( is_admin() && ! wp_doing_ajax() && ! parent::is_module_settings_page() ) {
142 + return;
106 143 }
107 144
108 145 // Enqueue styles.
109 146 add_action( 'merchant_enqueue_before_main_css_js', array( $this, 'enqueue_css' ) );
@@ -118,35 +155,92 @@
118 155 if ( defined( 'BOTIGA_PRO_URI' ) && defined( 'BOTIGA_PRO_VERSION' ) ) {
119 156 add_action( 'wp_enqueue_scripts', array( $this, 'modal_compatibility_with_botiga_theme' ) );
120 157 }
121 158
122 - // Modal content ajax callback.
123 - add_action( 'wp_ajax_merchant_quick_view_content', array( $this, 'modal_content_ajax_callback' ) );
124 - add_action( 'wp_ajax_nopriv_merchant_quick_view_content', array( $this, 'modal_content_ajax_callback' ) );
125 -
126 159 // Button Position.
127 - $button_position = Merchant_Admin_Options::get( self::MODULE_ID, 'button_position', 'overlay' );
160 + if ( ! $this->is_shortcode_enabled() ) {
161 + $button_position = Merchant_Admin_Options::get( self::MODULE_ID, 'button_position', 'overlay' );
128 162
129 - if ( 'before' === $button_position ) {
130 - add_action( 'woocommerce_after_shop_loop_item', array( $this, 'quick_view_button' ), 5 );
131 - } elseif ( 'after' === $button_position ) {
132 - add_action( 'woocommerce_after_shop_loop_item', array( $this, 'quick_view_button' ), 15 );
133 - } elseif ( 'overlay' === $button_position ) {
134 - if ( merchant_is_kadence_active() ) {
135 - add_filter( 'kadence_archive_content_wrap_start', array( $this, 'add_quick_view_button' ) );
136 - } else {
137 - add_action( 'woocommerce_after_shop_loop_item', array( $this, 'quick_view_button' ), 15 );
138 - }
139 - }
163 + if ( 'before' === $button_position ) {
164 + add_action( 'woocommerce_after_shop_loop_item', array( $this, 'quick_view_button' ), 5 );
165 + } elseif ( 'after' === $button_position ) {
166 + add_action( 'woocommerce_after_shop_loop_item', array( $this, 'quick_view_button' ), 15 );
167 + } elseif ( 'overlay' === $button_position ) {
168 + if ( merchant_is_kadence_active() ) {
169 + add_action( 'woocommerce_before_shop_loop_item_title', array( $this, 'quick_view_button' ), 35 );
170 + //add_filter( 'kadence_archive_content_wrap_start', array( $this, 'add_quick_view_button' ) );
171 + } elseif ( merchant_is_blocksy_active() ) {
172 + add_action( 'blocksy:woocommerce:product-card:thumbnail:end', array( $this, 'quick_view_button' ) );
173 + } elseif ( merchant_is_botiga_active() ) {
174 + add_action( 'woocommerce_before_shop_loop_item_title', array( $this, 'quick_view_button' ) );
175 + } elseif ( merchant_is_oceanwp_active() ) {
176 + add_action( 'ocean_after_archive_product_image', array( $this, 'quick_view_button' ) );
177 + } elseif ( merchant_is_flatsome_active() ) {
178 + add_action( 'flatsome_woocommerce_shop_loop_images', array( $this, 'quick_view_button' ) );
179 + } elseif ( merchant_is_storefront_active() ) {
180 + remove_action( 'woocommerce_before_shop_loop_item_title', 'woocommerce_template_loop_product_thumbnail', 10 );
181 + add_action( 'woocommerce_before_shop_loop_item_title', function () {
182 + echo '<div class="merchant-storefront-thumbnail-wrapper">';
183 + woocommerce_template_loop_product_thumbnail();
184 + $this->quick_view_button();
185 + echo '</div>';
186 + } );
187 + } elseif ( merchant_is_astra_active() ) {
188 + add_action( 'woocommerce_after_shop_loop_item', array( $this, 'quick_view_button' ), 7 );
189 + } else {
190 + add_action( 'woocommerce_after_shop_loop_item', array( $this, 'quick_view_button' ) );
191 + }
192 + }
193 + }
140 194
141 195 // Inject quick view modal output on footer.
142 196 add_action( 'wp_footer', array( $this, 'modal_output' ) );
143 197
198 + // Show Suggested Module
199 + $suggested_placement = Merchant_Admin_Options::get( self::MODULE_ID, 'suggested_products_placement', 'after_add_to_cart' );
200 + add_action( 'merchant_quick_view_' . $suggested_placement, array( $this, 'render_suggested_module_content' ), 10, 2 );
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 +
206 + // Show Buy Now Module
207 + add_action( 'woocommerce_after_add_to_cart_button', array( $this, 'render_buy_now_button' ) );
208 +
144 209 // Custom CSS.
145 210 add_filter( 'merchant_custom_css', array( $this, 'frontend_custom_css' ) );
211 +
212 + // Modal content ajax callback.
213 + add_action( 'wp_ajax_merchant_quick_view_content', array( $this, 'modal_content_ajax_callback' ) );
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();
146 218 }
147 219
148 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 + /**
230 + * Singleton
231 + *
232 + * @return self|null
233 + */
234 + public static function get_instance() {
235 + if ( self::$instance === null ) {
236 + self::$instance = new self();
237 + }
238 +
239 + return self::$instance;
240 + }
241 +
242 + /**
149 243 * Init translations.
150 244 *
151 245 * @return void
152 246 */
@@ -157,8 +251,44 @@
157 251 }
158 252 }
159 253
160 254 /**
255 + * Print shortcode content.
256 + *
257 + * @return string
258 + */
259 + public function shortcode_handler() {
260 + // Check if module is active.
261 + if ( ! Merchant_Modules::is_module_active( $this->module_id ) ) {
262 + return '';
263 + }
264 +
265 + // Check if shortcode is enabled.
266 + if ( ! $this->is_shortcode_enabled() ) {
267 + return '';
268 + }
269 +
270 + global $product;
271 +
272 + $product_id = is_object( $product ) ? $product->get_id() : get_the_ID();
273 +
274 + ob_start();
275 + $this->quick_view_button( 'shortcode' );
276 + $shortcode_content = ob_get_clean();
277 +
278 + /**
279 + * Filter the shortcode html content.
280 + *
281 + * @param string $shortcode_content shortcode html content
282 + * @param string $module_id module id
283 + * @param int $post_id product id
284 + *
285 + * @since 1.8
286 + */
287 + return apply_filters( 'merchant_module_shortcode_content_html', $shortcode_content, $this->module_id, $product_id );
288 + }
289 +
290 + /**
161 291 * Concatenate the quick view button with the content start wrap.
162 292 *
163 293 * @return string
164 294 */
@@ -197,10 +327,15 @@
197 327 *
198 328 * @return void
199 329 */
200 330 public function enqueue_scripts() {
201 - wp_enqueue_script( 'zoom' );
202 - 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 + }
203 338 wp_enqueue_script( 'wc-single-product' );
204 339 wp_enqueue_script( 'wc-add-to-cart-variation' );
205 340
206 341 // Register and enqueue the main module script.
@@ -215,10 +350,12 @@
215 350 */
216 351 public function localize_script( $setting ) {
217 352 $module_settings = $this->get_module_settings();
218 353
219 - $setting[ 'quick_view' ] = true;
220 - $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' ] );
221 358
222 359 return $setting;
223 360 }
224 361
@@ -335,149 +472,60 @@
335 472 }
336 473
337 474 /**
338 475 * Modal content ajax callback.
339 - * TODO: Render the output through templates files.
340 - *
341 - * @return void
476 + *
477 + * @return void
342 478 */
343 479 public function modal_content_ajax_callback() {
344 480 check_ajax_referer( 'merchant-nonce', 'nonce' );
345 -
481 +
346 482 if ( ! isset( $_POST['product_id'] ) || ! function_exists( 'wc_get_product' ) ) {
347 483 wp_send_json_error();
348 484 }
349 -
485 +
350 486 $args = array(
351 487 'product_id' => absint( $_POST['product_id'] ),
352 488 );
353 -
354 - global $product;
355 489
490 + global $product, $post;
491 +
356 492 $settings = $this->get_module_settings();
357 493 $product = wc_get_product( $args['product_id'] );
358 -
494 +
359 495 if ( is_wp_error( $product ) || empty( $product ) ) {
360 496 wp_send_json_error();
361 497 }
362 -
363 - $product_id = $product->get_id();
364 - $hide_quantity = ( empty( $settings[ 'show_quantity' ] ) ) ? 'merchant-hide-quantity' : '';
365 498
366 - ob_start();
367 -
368 - ?>
369 -
370 - <div id="product-<?php echo absint( $product_id ); ?>" <?php wc_product_class( '', $product ); ?>>
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
371 503
372 - <div class="merchant-quick-view-row">
504 + $content = merchant_get_template_part(
505 + self::MODULE_TEMPLATES,
506 + 'content',
507 + array(
508 + 'product' => $product,
509 + 'settings' => $settings,
510 + ),
511 + true
512 + );
373 513
374 - <div class="merchant-quick-view-column">
375 - <div class="merchant-quick-view-product-gallery">
376 - <?php woocommerce_show_product_images(); ?>
377 - </div>
378 - </div>
379 -
380 - <div class="merchant-quick-view-column">
381 -
382 - <div class="merchant-quick-view-summary product-gallery-summary">
383 -
384 - <div class="merchant-quick-view-product-title">
385 - <h2 class="product_title entry-title"><?php echo esc_html( $product->get_title() ); ?></h2>
386 - </div>
387 - <?php if ( 0 !== $product->get_average_rating() ) : ?>
388 - <div class="merchant-quick-view-product-rating">
389 - <div class="woocommerce-product-rating">
390 - <?php echo wp_kses( wc_get_rating_html( $product->get_average_rating() ), merchant_kses_allowed_tags() ); ?>
391 - </div>
392 - </div>
393 - <?php endif; ?>
394 - <div class="merchant-quick-view-product-price">
395 - <div class="price"><?php echo wp_kses( $product->get_price_html(), merchant_kses_allowed_tags() ); ?></div>
396 - </div>
397 - <?php if ( 'top' === $settings[ 'place_product_description' ] ) : ?>
398 - <?php if ( 'full' === $settings[ 'description_style' ] ) : ?>
399 - <div class="merchant-quick-view-product-excerpt">
400 - <?php echo wp_kses_post( $product->get_description() ); ?>
401 - </div>
402 - <?php else : ?>
403 - <div class="merchant-quick-view-product-excerpt">
404 - <p><?php echo wp_kses_post( $product->get_short_description() ); ?></p>
405 - </div>
406 - <?php endif; ?>
407 - <?php endif; ?>
408 - <div class="merchant-quick-view-product-add-to-cart <?php echo esc_attr( $hide_quantity ); ?>">
409 - <?php woocommerce_template_single_add_to_cart(); ?>
410 - </div>
411 - <?php if ( 'bottom' === $settings[ 'place_product_description' ] ) : ?>
412 - <?php if ( 'full' === $settings[ 'description_style' ] ) : ?>
413 - <div class="merchant-quick-view-product-excerpt">
414 - <?php echo wp_kses_post( $product->get_description() ); ?>
415 - </div>
416 - <?php else : ?>
417 - <div class="merchant-quick-view-product-excerpt">
418 - <p><?php echo wp_kses_post( $product->get_short_description() ); ?></p>
419 - </div>
420 - <?php endif; ?>
421 - <?php endif; ?>
422 - <div class="merchant-quick-view-product-meta">
423 - <?php woocommerce_template_single_meta(); ?>
424 - </div>
425 -
426 - <?php
427 - /**
428 - * Hook 'merchant_quick_view_after_product_excerpt'
429 - *
430 - * @since 1.0.0
431 - */
432 - do_action( 'merchant_quick_view_after_product_meta' );
433 - ?>
434 -
435 - </div>
436 -
437 - </div>
438 -
439 - </div>
440 -
441 - </div>
442 -
443 - <?php
444 -
445 - $content = ob_get_contents();
446 -
447 - ob_get_clean();
448 -
449 514 wp_send_json_success( $content );
450 515 }
451 516
517 +
452 518 /**
453 519 * Modal output.
454 - * TODO: Render the output through templates files.
455 - *
520 + *
456 521 * @return void
457 522 */
458 523 public function modal_output() {
459 524 $settings = $this->get_module_settings();
525 + $template = ( 'side-panel' === $settings[ 'modal_layout' ] ) ? 'side-panel' : 'modal';
460 526
461 - ?>
462 - <div class="single-product merchant-quick-view-modal merchant-quick-view-<?php echo esc_attr( $settings[ 'place_product_image' ] ); ?>">
463 - <div class="merchant-quick-view-overlay"></div>
464 - <div class="merchant-quick-view-loader">
465 - <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
466 - <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" />
467 - <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" />
468 - </svg>
469 - </div>
470 - <div class="merchant-quick-view-inner">
471 - <a href="#" class="merchant-quick-view-close-button" title="<?php echo esc_attr__( 'Close quick view modal', 'merchant' ); ?>">
472 - <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512">
473 - <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"/>
474 - </svg>
475 - </a>
476 - <div class="merchant-quick-view-content"></div>
477 - </div>
478 - </div>
479 - <?php
527 + merchant_get_template_part( self::MODULE_TEMPLATES, $template, array( 'settings' => $settings ) );
480 528 }
481 529
482 530 /**
483 531 * Quick view button.
@@ -483,29 +531,244 @@
483 531 * Quick view button.
484 532 *
485 533 * @return void
486 534 */
487 - public function quick_view_button() {
535 + public function quick_view_button( $context = '' ) {
488 536 global $product;
489 537
490 - $settings = $this->get_module_settings();
538 + if ( ! is_object( $product ) ) {
539 + return;
540 + }
541 +
542 + $settings = $this->get_module_settings();
491 543 $product_id = $product->get_id();
492 -
544 +
493 545 $button_text_html = '';
494 546 $button_icon_html = '';
495 -
547 +
496 548 if ( 'icon' === $settings[ 'button_type' ] || 'icon-text' === $settings[ 'button_type' ] ) {
497 549 $button_icon_html = Merchant_SVG_Icons::get_svg_icon( $settings[ 'button_icon' ] );
498 550 }
499 -
551 +
500 552 if ( 'text' === $settings[ 'button_type' ] || 'icon-text' === $settings[ 'button_type' ] ) {
501 553 $button_text_html = '<span>' . Merchant_Translator::translate( $settings[ 'button_text' ] ) . '</span>';
502 554 }
503 -
555 +
556 + $classes = 'button wp-element-button merchant-quick-view-button';
557 + $classes .= $context !== 'shortcode' ? ' merchant-quick-view-position-' . ( $settings[ 'button_position' ] ?? '' ) : '';
558 + $classes .= ( $context !== 'shortcode' && ! empty( $settings['mobile_position'] ) ) ? ' merchant-quick-view-position-has-mobile-position' : '';
504 559 ?>
505 - <a href="#" class="button wp-element-button merchant-quick-view-button merchant-quick-view-position-<?php echo esc_attr( $settings[ 'button_position' ] ); ?>" data-product-id="<?php echo absint( $product_id ); ?>"><?php echo wp_kses( $button_icon_html, merchant_kses_allowed_tags( array(), false ) ) . wp_kses_post( $button_text_html ); ?></a>
560 + <button class="<?php echo esc_attr( $classes ); ?>" data-product-id="<?php echo absint( $product_id ); ?>" type="button">
561 + <?php echo wp_kses( $button_icon_html, merchant_kses_allowed_tags( array(), false ) ) . wp_kses_post( $button_text_html ); ?>
562 + </button>
506 563 <?php
507 564 }
565 +
566 + /**
567 + * Renders the Buy Now button.
568 + *
569 + * @return void
570 + */
571 + public function render_buy_now_button() {
572 + if ( ! Merchant_Modules::is_module_active( Merchant_Buy_Now::MODULE_ID ) ) {
573 + return;
574 + }
575 +
576 + // Don't include on Single Product
577 + if ( ! did_action( 'merchant_quick_view_before_add_to_cart' ) ) {
578 + return;
579 + }
580 +
581 + $show_buy_now = (bool) Merchant_Admin_Options::get( $this->module_id, 'show_buy_now_button', false );
582 + if ( ! $show_buy_now ) {
583 + return;
584 + }
585 +
586 + global $product;
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 +
603 + $text = Merchant_Admin_Options::get( Merchant_Buy_Now::MODULE_ID, 'button-text', esc_html__( 'Buy Now', 'merchant' ) );
604 +
605 + $_wrapper_classes = array();
606 + $_wrapper_classes[] = $product->get_type() === 'variable' ? 'disabled' : '';
607 +
608 + /**
609 + * Hook 'merchant_module_buy_now_wrapper_class'
610 + *
611 + * @since 1.8
612 + */
613 + $wrapper_classes = apply_filters( 'merchant_module_buy_now_wrapper_class', $_wrapper_classes );
614 + ?>
615 + <!-- Don't define type="submit" because it creates issue with block themes. The button is inside the form, so by default the type is already "submit". -->
616 + <button name="merchant-buy-now" value="<?php echo absint( $product->get_ID() ); ?>" class="button alt wp-element-button merchant-buy-now-button <?php echo esc_attr( implode( ' ', $wrapper_classes ) ); ?>"><?php echo esc_html( Merchant_Translator::translate( $text ) ); ?></button>
617 + <?php
618 + }
619 +
620 + /**
621 + * Renders the suggested module content based on the provided settings.
622 + *
623 + * @param $product
624 + * @param $settings
625 + *
626 + * @return void
627 + */
628 + public function render_suggested_module_content( $product, $settings ) {
629 + $show_suggested = (bool) Merchant_Admin_Options::get( $this->module_id, 'show_suggested_products', true );
630 +
631 + if ( ! $show_suggested || empty( $product ) ) {
632 + return;
633 + }
634 +
635 + $suggested_module = $settings['suggested_products_module'] ?? 'bulk_discounts';
636 +
637 + switch ( $suggested_module ) {
638 + case 'bulk_discounts':
639 + $this->print_bulk_discounts( $product );
640 + break;
641 +
642 + case 'buy_x_get_y':
643 + $this->print_buy_x_get_y( $product );
644 + break;
645 +
646 + case 'frequently_bought_together':
647 + $this->print_frequently_bought_together( $product );
648 + break;
649 + }
650 + }
651 +
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 + /**
672 + * Prints the bulk discounts for the specified product.
673 + *
674 + * @param $product
675 + *
676 + * @return void
677 + */
678 + public function print_bulk_discounts( $product ) {
679 + if ( ! merchant_is_pro_active() || ! Merchant_Modules::is_module_active( Merchant_Volume_Discounts::MODULE_ID ) ) {
680 + return;
681 + }
682 +
683 + $product_id = $product->get_id();
684 + $discount_tiers = Merchant_Pro_Volume_Discounts::availabe_offers( $product_id );
685 +
686 + if ( ! empty( $discount_tiers ) ) {
687 + merchant_get_template_part(
688 + Merchant_Volume_Discounts::MODULE_TEMPLATES_PATH,
689 + 'single-product',
690 + array(
691 + 'settings' => Merchant_Admin_Options::get_all( Merchant_Volume_Discounts::MODULE_ID ),
692 + 'product' => $product,
693 + 'discount_tiers' => $discount_tiers,
694 + 'product_price' => $product->get_price(),
695 + 'in_cart' => Merchant_Pro_Volume_Discounts::is_in_cart( $product_id ),
696 + 'product_cart_quantity' => Merchant_Pro_Volume_Discounts::get_product_cart_quantity( $product_id ),
697 + 'product_id' => $product_id,
698 + )
699 + );
700 + }
701 + }
702 +
703 + /**
704 + * Prints the Buy X Get Y offers for the specified product.
705 + *
706 + * @param $product
707 + *
708 + * @return void
709 + */
710 + public function print_buy_x_get_y( $product ) {
711 + if ( ! merchant_is_pro_active() || ! Merchant_Modules::is_module_active( Merchant_Buy_X_Get_Y::MODULE_ID ) ) {
712 + return;
713 + }
714 +
715 + $eligibility = Merchant_Pro_BXGY_Service_Provider::eligibility();
716 +
717 + if ( ! $eligibility ) {
718 + return;
719 + }
720 +
721 + $product_id = $product->get_id();
722 + $offers = $eligibility->get_eligible_for_product( $product_id );
723 +
724 + if ( ! empty( $offers ) ) {
725 + merchant_get_template_part(
726 + Merchant_Buy_X_Get_Y::MODULE_TEMPLATES,
727 + 'single-product',
728 + array(
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,
734 + )
735 + );
736 + }
737 + }
738 +
739 + /**
740 + * Prints the frequently bought together bundles for the specified product.
741 + *
742 + * @param $product
743 + *
744 + * @return void
745 + */
746 + public function print_frequently_bought_together( $product ) {
747 + if ( ! merchant_is_pro_active() || ! Merchant_Modules::is_module_active( Merchant_Frequently_Bought_Together::MODULE_ID ) ) {
748 + return;
749 + }
750 +
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 );
757 +
758 + if ( ! empty( $bundles ) ) {
759 + merchant_get_template_part(
760 + Merchant_Frequently_Bought_Together::MODULE_TEMPLATES_PATH,
761 + 'single-product-v3',
762 + array(
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,
767 + )
768 + );
769 + }
770 + }
508 771
509 772 /**
510 773 * Custom CSS.
511 774 *
@@ -517,15 +780,15 @@
517 780 // Button Icon Color.
518 781 $css .= Merchant_Custom_CSS::get_variable_css( 'quick-view', 'icon-color', '#ffffff', '.merchant-quick-view-button', '--mrc-qv-button-icon-color' );
519 782
520 783 // Button Icon Color (hover).
521 - $css .= Merchant_Custom_CSS::get_variable_css( 'quick-view', 'icon-hover-color', '#ffffff', '.merchant-quick-view-button', '--mrc-qv-button-icon-hover-color' );
784 + $css .= Merchant_Custom_CSS::get_variable_css( 'quick-view', 'icon-hover-color', '#ffffff', '.merchant-quick-view-button', '--mrc-qv-button-icon-color-hover' );
522 785
523 786 // Button Text Color.
524 - $css .= Merchant_Custom_CSS::get_variable_css( 'quick-view', 'text-color', '#ffffff', '.merchant-quick-view-button', '--mrc-qv-button-color' );
787 + $css .= Merchant_Custom_CSS::get_variable_css( 'quick-view', 'text-color', '#ffffff', '.merchant-quick-view-button', '--mrc-qv-button-text-color' );
525 788
526 789 // Button Text Color (hover).
527 - $css .= Merchant_Custom_CSS::get_variable_css( 'quick-view', 'text-hover-color', '#ffffff', '.merchant-quick-view-button', '--mrc-qv-button-color-hover' );
790 + $css .= Merchant_Custom_CSS::get_variable_css( 'quick-view', 'text-hover-color', '#ffffff', '.merchant-quick-view-button', '--mrc-qv-button-text-color-hover' );
528 791
529 792 // Button Border Color.
530 793 $css .= Merchant_Custom_CSS::get_variable_css( 'quick-view', 'border-color', '#212121', '.merchant-quick-view-button', '--mrc-qv-button-border-color' );
531 794
@@ -540,11 +803,17 @@
540 803
541 804 // Button Position Top.
542 805 $css .= Merchant_Custom_CSS::get_variable_css( 'quick-view', 'button-position-top', '50', '.merchant-quick-view-button', '--mrc-qv-button-position-top', '%' );
543 806
807 + // Button Position Top (Mobile).
808 + $css .= Merchant_Custom_CSS::get_variable_css( 'quick-view', 'button-position-top-mobile', '50', '.merchant-quick-view-button', '--mrc-qv-button-position-top-mobile', '%' );
809 +
544 810 // Button Position Left.
545 811 $css .= Merchant_Custom_CSS::get_variable_css( 'quick-view', 'button-position-left', '50', '.merchant-quick-view-button', '--mrc-qv-button-position-left', '%' );
546 -
812 +
813 + // Button Position Left (Mobile).
814 + $css .= Merchant_Custom_CSS::get_variable_css( 'quick-view', 'button-position-left-mobile', '50', '.merchant-quick-view-button', '--mrc-qv-button-position-left-mobile', '%' );
815 +
547 816 // Modal Width.
548 817 $css .= Merchant_Custom_CSS::get_variable_css( 'quick-view', 'modal_width', 1000, '.merchant-quick-view-modal', '--mrc-qv-modal-width', 'px' );
549 818
550 819 // Modal Height.
@@ -549,8 +818,11 @@
549 818
550 819 // Modal Height.
551 820 $css .= Merchant_Custom_CSS::get_variable_css( 'quick-view', 'modal_height', 500, '.merchant-quick-view-modal', '--mrc-qv-modal-height', 'px' );
552 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 +
553 825 // Sale Price Color.
554 826 $css .= Merchant_Custom_CSS::get_variable_css( 'quick-view', 'sale-price-color', '#212121', '.merchant-quick-view-modal', '--mrc-qv-modal-sale-price-color' );
555 827
556 828 // Regular Price Color.
@@ -579,8 +851,30 @@
579 851 */
580 852 public function frontend_custom_css( $css ) {
581 853 $css .= $this->get_module_custom_css();
582 854
855 + $theme = wp_get_theme();
856 + $theme_name = $theme->get( 'Name' );
857 +
858 + if ( 'Astra' === $theme_name ) {
859 + $css .= '
860 + .astra-shop-thumbnail-wrap {
861 + position: relative;
862 + }
863 + .astra-shop-thumbnail-wrap img {
864 + width: 100%;
865 + }
866 + ';
867 + }
868 +
869 + if ( 'Storefront' === $theme_name ) {
870 + $css .= '
871 + .merchant-storefront-thumbnail-wrapper {
872 + position: relative;
873 + }
874 + ';
875 + }
876 +
583 877 return $css;
584 878 }
585 879 }
586 880
@@ -585,6 +879,10 @@
585 879 }
586 880
587 881 // Initialize the module.
588 882 add_action( 'init', function() {
589 - new Merchant_Quick_View();
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() );
590 888 } );