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
merchant / inc / modules / quick-view / class-quick-view.php

class-quick-view.php in Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant 2.3.2, at inc/modules/quick-view/class-quick-view.php

889 lines 29.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Quick View.
5 *
6 * @package Merchant
7 */
8
9 if ( ! defined( 'ABSPATH' ) ) {
10 exit; // Exit if accessed directly
11 }
12
13 /**
14 * Quick View Class.
15 *
16 */
17 class Merchant_Quick_View extends Merchant_Add_Module {
18
19 /**
20 * Module ID.
21 *
22 */
23 const MODULE_ID = 'quick-view';
24
25 /**
26 * Module templates path.
27 *
28 */
29 const MODULE_TEMPLATES = 'modules/' . self::MODULE_ID;
30
31 /**
32 * Is module preview.
33 *
34 */
35 public static $is_module_preview = false;
36
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 /**
56 * Constructor.
57 *
58 */
59 public function __construct() {
60
61 // Module id.
62 $this->module_id = self::MODULE_ID;
63
64 // WooCommerce only.
65 $this->wc_only = true;
66
67 // Parent construct.
68 parent::__construct();
69
70 // Module section.
71 $this->module_section = 'convert-more';
72
73 // Module default settings.
74 $this->module_default_settings = array(
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',
96 );
97
98 // Module data.
99 $this->module_data = Merchant_Admin_Modules::$modules_data[ self::MODULE_ID ];
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
112 // Module options path.
113 $this->module_options_path = MERCHANT_DIR . 'inc/modules/' . self::MODULE_ID . '/admin/options.php';
114
115 // Is module preview page.
116 if ( is_admin() && parent::is_module_settings_page() ) {
117 self::$is_module_preview = true;
118
119 // Enqueue admin styles.
120 add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_css' ) );
121
122 // Admin preview box.
123 add_filter( 'merchant_module_preview', array( $this, 'render_admin_preview' ), 10, 2 );
124
125 // Custom CSS.
126 // The custom CSS should be added here as well due to ensure preview box works properly.
127 add_filter( 'merchant_custom_css', array( $this, 'admin_custom_css' ) );
128
129 }
130
131 if ( Merchant_Modules::is_module_active( self::MODULE_ID ) && is_admin() ) {
132 // Init translations.
133 $this->init_translations();
134 }
135
136 if ( ! Merchant_Modules::is_module_active( self::MODULE_ID ) ) {
137 return;
138 }
139
140 // Return early if it's on admin but not in the respective module settings page.
141 if ( is_admin() && ! wp_doing_ajax() && ! parent::is_module_settings_page() ) {
142 return;
143 }
144
145 // Enqueue styles.
146 add_action( 'merchant_enqueue_before_main_css_js', array( $this, 'enqueue_css' ) );
147
148 // Enqueue scripts.
149 add_action( 'merchant_enqueue_after_main_css_js', array( $this, 'enqueue_scripts' ) );
150
151 // Localize script.
152 add_filter( 'merchant_localize_script', array( $this, 'localize_script' ) );
153
154 // Handle Botiga theme scripts for compatibility.
155 if ( defined( 'BOTIGA_PRO_URI' ) && defined( 'BOTIGA_PRO_VERSION' ) ) {
156 add_action( 'wp_enqueue_scripts', array( $this, 'modal_compatibility_with_botiga_theme' ) );
157 }
158
159 // Button Position.
160 if ( ! $this->is_shortcode_enabled() ) {
161 $button_position = Merchant_Admin_Options::get( self::MODULE_ID, 'button_position', 'overlay' );
162
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 }
194
195 // Inject quick view modal output on footer.
196 add_action( 'wp_footer', array( $this, 'modal_output' ) );
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
209 // Custom CSS.
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();
218 }
219
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 /**
243 * Init translations.
244 *
245 * @return void
246 */
247 public function init_translations() {
248 $settings = $this->get_module_settings();
249 if ( ! empty( $settings['button_text'] ) ) {
250 Merchant_Translator::register_string( $settings['button_text'], esc_html__( 'Quick view button text', 'merchant' ) );
251 }
252 }
253
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 /**
291 * Concatenate the quick view button with the content start wrap.
292 *
293 * @return string
294 */
295 public function add_quick_view_button() {
296 return $this->quick_view_button() . '<div class="product-details content-bg entry-content-wrap">';
297 }
298
299 /**
300 * Admin enqueue CSS.
301 *
302 * @return void
303 */
304 public function admin_enqueue_css() {
305 $page = ( ! empty( $_GET['page'] ) ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
306 $module = ( ! empty( $_GET['module'] ) ) ? sanitize_text_field( wp_unslash( $_GET['module'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
307
308 if ( 'merchant' === $page && self::MODULE_ID === $module ) {
309 wp_enqueue_style( 'merchant-' . self::MODULE_ID, MERCHANT_URI . 'assets/css/modules/' . self::MODULE_ID . '/quick-view.min.css', array(), MERCHANT_VERSION );
310 wp_enqueue_style( 'merchant-admin-' . self::MODULE_ID, MERCHANT_URI . 'assets/css/modules/' . self::MODULE_ID . '/admin/preview.min.css', array(), MERCHANT_VERSION );
311 }
312 }
313
314 /**
315 * Enqueue CSS.
316 *
317 * @return void
318 */
319 public function enqueue_css() {
320
321 // Specific module styles.
322 wp_enqueue_style( 'merchant-' . self::MODULE_ID, MERCHANT_URI . 'assets/css/modules/' . self::MODULE_ID . '/quick-view.min.css', array(), MERCHANT_VERSION );
323 }
324
325 /**
326 * Enqueue scripts.
327 *
328 * @return void
329 */
330 public function enqueue_scripts() {
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 }
338 wp_enqueue_script( 'wc-single-product' );
339 wp_enqueue_script( 'wc-add-to-cart-variation' );
340
341 // Register and enqueue the main module script.
342 wp_enqueue_script( 'merchant-' . self::MODULE_ID, MERCHANT_URI . 'assets/js/modules/' . self::MODULE_ID . '/quick-view.min.js', array(), MERCHANT_VERSION, true );
343 }
344
345 /**
346 * Localize script with module settings.
347 *
348 * @param array $setting The merchant global object setting parameter.
349 * @return array $setting The merchant global object setting parameter.
350 */
351 public function localize_script( $setting ) {
352 $module_settings = $this->get_module_settings();
353
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' ] );
358
359 return $setting;
360 }
361
362 /**
363 * Enqueue botiga theme scripts.
364 *
365 * @return void
366 */
367 public function modal_compatibility_with_botiga_theme() {
368 if ( ! wp_script_is( 'botiga-product-swatch' ) ) {
369 wp_enqueue_script( 'botiga-product-swatch', BOTIGA_PRO_URI . 'assets/js/botiga-product-swatch.min.js', array(), BOTIGA_PRO_VERSION, true );
370 }
371
372 if ( ! wp_script_is( 'botiga-checkout-quantity-input' ) ) {
373 wp_enqueue_script( 'botiga-checkout-quantity-input', BOTIGA_PRO_URI . 'assets/js/botiga-checkout-quantity-input.min.js', array( 'jquery' ), BOTIGA_PRO_VERSION, true );
374 }
375 }
376
377 /**
378 * Render admin preview
379 *
380 * @param Merchant_Admin_Preview $preview
381 * @param string $module
382 *
383 * @return Merchant_Admin_Preview
384 */
385 public function render_admin_preview( $preview, $module ) {
386 if ( self::MODULE_ID === $module ) {
387 ob_start();
388 self::admin_preview_content();
389 $content = ob_get_clean();
390
391 // HTML.
392 $preview->set_html( $content );
393
394 // Button Type.
395 $preview->set_class( 'button_type', '.merchant-quick-view-button', array( 'icon', 'icon-text', 'text' ) );
396
397 // Button Text.
398 $preview->set_text( 'button_text', '.button-text' );
399
400 // Button Icon.
401 $preview->set_svg_icon( 'button_icon', '.quick-view-icon' );
402
403 // Button Position.
404 $preview->set_class( 'button_position', '.merchant-quick-view-preview', array( 'before', 'after', 'overlay' ) );
405
406 }
407
408 return $preview;
409 }
410
411 /**
412 * Admin preview content.
413 *
414 * @return void
415 */
416 public function admin_preview_content() {
417 $settings = $this->get_module_settings();
418
419 ?>
420
421 <div class="merchant-quick-view-preview <?php echo esc_attr( $settings[ 'button_position' ] ); ?>">
422 <div class="image-wrapper">
423 <div class="button-position button-position-overlay">
424 <?php $this->admin_preview_quick_view_button(); ?>
425 </div>
426 </div>
427 <h3><?php echo esc_html__( 'Product Title', 'merchant' ); ?></h3>
428 <p><?php echo esc_html__( 'The product description normally is displayed here.', 'merchant' ); ?></p>
429 <div class="button-position button-position-before">
430 <?php $this->admin_preview_quick_view_button(); ?>
431 </div>
432 <div>
433 <a href="#" class="add_to_cart_button"><?php echo esc_html__( 'Add To Cart', 'merchant' ); ?></a>
434 </div>
435 <div class="button-position button-position-after">
436 <?php $this->admin_preview_quick_view_button(); ?>
437 </div>
438
439 </div>
440
441 <?php
442 }
443
444 /**
445 * Admin preview quick view button.
446 *
447 * @return void
448 */
449 public function admin_preview_quick_view_button() {
450 $settings = $this->get_module_settings();
451
452 ?>
453
454 <a href="#" class="merchant-quick-view-button <?php echo esc_attr( $settings[ 'button_type' ] ); ?>">
455 <span class="button-type button-type-icon">
456 <span class="quick-view-icon">
457 <?php echo wp_kses( Merchant_SVG_Icons::get_svg_icon( $settings[ 'button_icon' ] ), merchant_kses_allowed_tags( array(), false ) ); ?>
458 </span>
459 </span>
460 <span class="button-type button-type-icon-text">
461 <span class="quick-view-icon">
462 <?php echo wp_kses( Merchant_SVG_Icons::get_svg_icon( $settings[ 'button_icon' ] ), merchant_kses_allowed_tags( array(), false ) ); ?>
463 </span>
464 <span class="button-text"><?php echo esc_html( $settings[ 'button_text' ] ); ?></span>
465 </span>
466 <span class="button-type button-type-text">
467 <span class="button-text"><?php echo esc_html( $settings[ 'button_text' ] ); ?></span>
468 </span>
469 </a>
470
471 <?php
472 }
473
474 /**
475 * Modal content ajax callback.
476 *
477 * @return void
478 */
479 public function modal_content_ajax_callback() {
480 check_ajax_referer( 'merchant-nonce', 'nonce' );
481
482 if ( ! isset( $_POST['product_id'] ) || ! function_exists( 'wc_get_product' ) ) {
483 wp_send_json_error();
484 }
485
486 $args = array(
487 'product_id' => absint( $_POST['product_id'] ),
488 );
489
490 global $product, $post;
491
492 $settings = $this->get_module_settings();
493 $product = wc_get_product( $args['product_id'] );
494
495 if ( is_wp_error( $product ) || empty( $product ) ) {
496 wp_send_json_error();
497 }
498
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
503
504 $content = merchant_get_template_part(
505 self::MODULE_TEMPLATES,
506 'content',
507 array(
508 'product' => $product,
509 'settings' => $settings,
510 ),
511 true
512 );
513
514 wp_send_json_success( $content );
515 }
516
517
518 /**
519 * Modal output.
520 *
521 * @return void
522 */
523 public function modal_output() {
524 $settings = $this->get_module_settings();
525 $template = ( 'side-panel' === $settings[ 'modal_layout' ] ) ? 'side-panel' : 'modal';
526
527 merchant_get_template_part( self::MODULE_TEMPLATES, $template, array( 'settings' => $settings ) );
528 }
529
530 /**
531 * Quick view button.
532 *
533 * @return void
534 */
535 public function quick_view_button( $context = '' ) {
536 global $product;
537
538 if ( ! is_object( $product ) ) {
539 return;
540 }
541
542 $settings = $this->get_module_settings();
543 $product_id = $product->get_id();
544
545 $button_text_html = '';
546 $button_icon_html = '';
547
548 if ( 'icon' === $settings[ 'button_type' ] || 'icon-text' === $settings[ 'button_type' ] ) {
549 $button_icon_html = Merchant_SVG_Icons::get_svg_icon( $settings[ 'button_icon' ] );
550 }
551
552 if ( 'text' === $settings[ 'button_type' ] || 'icon-text' === $settings[ 'button_type' ] ) {
553 $button_text_html = '<span>' . Merchant_Translator::translate( $settings[ 'button_text' ] ) . '</span>';
554 }
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' : '';
559 ?>
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>
563 <?php
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 }
771
772 /**
773 * Custom CSS.
774 *
775 * @return string
776 */
777 public function get_module_custom_css() {
778 $css = '';
779
780 // Button Icon Color.
781 $css .= Merchant_Custom_CSS::get_variable_css( 'quick-view', 'icon-color', '#ffffff', '.merchant-quick-view-button', '--mrc-qv-button-icon-color' );
782
783 // Button Icon Color (hover).
784 $css .= Merchant_Custom_CSS::get_variable_css( 'quick-view', 'icon-hover-color', '#ffffff', '.merchant-quick-view-button', '--mrc-qv-button-icon-color-hover' );
785
786 // Button Text Color.
787 $css .= Merchant_Custom_CSS::get_variable_css( 'quick-view', 'text-color', '#ffffff', '.merchant-quick-view-button', '--mrc-qv-button-text-color' );
788
789 // Button Text 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' );
791
792 // Button Border Color.
793 $css .= Merchant_Custom_CSS::get_variable_css( 'quick-view', 'border-color', '#212121', '.merchant-quick-view-button', '--mrc-qv-button-border-color' );
794
795 // Button Border Color (hover).
796 $css .= Merchant_Custom_CSS::get_variable_css( 'quick-view', 'border-hover-color', '#414141', '.merchant-quick-view-button', '--mrc-qv-button-border-color-hover' );
797
798 // Button Background Color.
799 $css .= Merchant_Custom_CSS::get_variable_css( 'quick-view', 'background-color', '#212121', '.merchant-quick-view-button', '--mrc-qv-button-bg-color' );
800
801 // Button Background Color (hover).
802 $css .= Merchant_Custom_CSS::get_variable_css( 'quick-view', 'background-hover-color', '#414141', '.merchant-quick-view-button', '--mrc-qv-button-bg-color-hover' );
803
804 // Button Position Top.
805 $css .= Merchant_Custom_CSS::get_variable_css( 'quick-view', 'button-position-top', '50', '.merchant-quick-view-button', '--mrc-qv-button-position-top', '%' );
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
810 // Button Position Left.
811 $css .= Merchant_Custom_CSS::get_variable_css( 'quick-view', 'button-position-left', '50', '.merchant-quick-view-button', '--mrc-qv-button-position-left', '%' );
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
816 // Modal Width.
817 $css .= Merchant_Custom_CSS::get_variable_css( 'quick-view', 'modal_width', 1000, '.merchant-quick-view-modal', '--mrc-qv-modal-width', 'px' );
818
819 // Modal Height.
820 $css .= Merchant_Custom_CSS::get_variable_css( 'quick-view', 'modal_height', 500, '.merchant-quick-view-modal', '--mrc-qv-modal-height', 'px' );
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
825 // Sale Price Color.
826 $css .= Merchant_Custom_CSS::get_variable_css( 'quick-view', 'sale-price-color', '#212121', '.merchant-quick-view-modal', '--mrc-qv-modal-sale-price-color' );
827
828 // Regular Price Color.
829 $css .= Merchant_Custom_CSS::get_variable_css( 'quick-view', 'regular-price-color', '#414141', '.merchant-quick-view-modal', '--mrc-qv-modal-regular-price-color' );
830
831 return $css;
832 }
833
834 /**
835 * Admin custom CSS.
836 *
837 * @param string $css The custom CSS.
838 * @return string $css The custom CSS.
839 */
840 public function admin_custom_css( $css ) {
841 $css .= $this->get_module_custom_css();
842
843 return $css;
844 }
845
846 /**
847 * Frontend custom CSS.
848 *
849 * @param string $css The custom CSS.
850 * @return string $css The custom CSS.
851 */
852 public function frontend_custom_css( $css ) {
853 $css .= $this->get_module_custom_css();
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
877 return $css;
878 }
879 }
880
881 // Initialize the module.
882 add_action( 'init', function() {
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() );
888 } );
889