PluginProbe
Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant / 2.2.8
Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant v2.2.8
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.2.8, at inc/modules/quick-view/class-quick-view.php

964 lines 32.3 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 * Is module preview.
27 *
28 */
29 public static $is_module_preview = false;
30
31 /**
32 * Whether the module has a shortcode or not.
33 *
34 * @var bool
35 */
36 public $has_shortcode = true;
37
38 private static $instance = null;
39
40 /**
41 * Constructor.
42 *
43 */
44 public function __construct() {
45
46 // Module id.
47 $this->module_id = self::MODULE_ID;
48
49 // WooCommerce only.
50 $this->wc_only = true;
51
52 // Parent construct.
53 parent::__construct();
54
55 // Module section.
56 $this->module_section = 'convert-more';
57
58 // Module default settings.
59 $this->module_default_settings = array(
60 'button_type' => 'text',
61 'button_text' => __( 'Quick View', 'merchant' ),
62 'button_icon' => 'eye',
63 'button_position' => 'overlay',
64 'button-position-top' => 50,
65 'button-position-left' => 50,
66 'mobile_position' => false,
67 'button-position-top-mobile' => 50,
68 'button-position-left-mobile' => 50,
69 'zoom_effect' => 1,
70 'show_quantity' => 1,
71 'place_product_description' => 'top',
72 'description_style' => 'short',
73 'place_product_image' => 'thumbs-at-left',
74 'show_buy_now_button' => false,
75 'show_suggested_products' => true,
76 'suggested_products_module' => 'bulk_discounts',
77 'suggested_products_placement' => 'after_add_to_cart',
78 );
79
80 // Module data.
81 $this->module_data = Merchant_Admin_Modules::$modules_data[ self::MODULE_ID ];
82
83 // Module options path.
84 $this->module_options_path = MERCHANT_DIR . 'inc/modules/' . self::MODULE_ID . '/admin/options.php';
85
86 // Is module preview page.
87 if ( is_admin() && parent::is_module_settings_page() ) {
88 self::$is_module_preview = true;
89
90 // Enqueue admin styles.
91 add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_css' ) );
92
93 // Admin preview box.
94 add_filter( 'merchant_module_preview', array( $this, 'render_admin_preview' ), 10, 2 );
95
96 // Custom CSS.
97 // The custom CSS should be added here as well due to ensure preview box works properly.
98 add_filter( 'merchant_custom_css', array( $this, 'admin_custom_css' ) );
99
100 }
101
102 if ( Merchant_Modules::is_module_active( self::MODULE_ID ) && is_admin() ) {
103 // Init translations.
104 $this->init_translations();
105 }
106
107 if ( ! Merchant_Modules::is_module_active( self::MODULE_ID ) ) {
108 return;
109 }
110
111 // Return early if it's on admin but not in the respective module settings page.
112 if ( is_admin() && ! wp_doing_ajax() && ! parent::is_module_settings_page() ) {
113 return;
114 }
115
116 // Enqueue styles.
117 add_action( 'merchant_enqueue_before_main_css_js', array( $this, 'enqueue_css' ) );
118
119 // Enqueue scripts.
120 add_action( 'merchant_enqueue_after_main_css_js', array( $this, 'enqueue_scripts' ) );
121
122 // Localize script.
123 add_filter( 'merchant_localize_script', array( $this, 'localize_script' ) );
124
125 // Handle Botiga theme scripts for compatibility.
126 if ( defined( 'BOTIGA_PRO_URI' ) && defined( 'BOTIGA_PRO_VERSION' ) ) {
127 add_action( 'wp_enqueue_scripts', array( $this, 'modal_compatibility_with_botiga_theme' ) );
128 }
129
130 // Button Position.
131 if ( ! $this->is_shortcode_enabled() ) {
132 $button_position = Merchant_Admin_Options::get( self::MODULE_ID, 'button_position', 'overlay' );
133
134 if ( 'before' === $button_position ) {
135 add_action( 'woocommerce_after_shop_loop_item', array( $this, 'quick_view_button' ), 5 );
136 } elseif ( 'after' === $button_position ) {
137 add_action( 'woocommerce_after_shop_loop_item', array( $this, 'quick_view_button' ), 15 );
138 } elseif ( 'overlay' === $button_position ) {
139 if ( merchant_is_kadence_active() ) {
140 add_action( 'woocommerce_before_shop_loop_item_title', array( $this, 'quick_view_button' ), 35 );
141 //add_filter( 'kadence_archive_content_wrap_start', array( $this, 'add_quick_view_button' ) );
142 } elseif ( merchant_is_blocksy_active() ) {
143 add_action( 'blocksy:woocommerce:product-card:thumbnail:end', array( $this, 'quick_view_button' ) );
144 } elseif ( merchant_is_botiga_active() ) {
145 add_action( 'woocommerce_before_shop_loop_item_title', array( $this, 'quick_view_button' ) );
146 } elseif ( merchant_is_oceanwp_active() ) {
147 add_action( 'ocean_after_archive_product_image', array( $this, 'quick_view_button' ) );
148 } elseif ( merchant_is_flatsome_active() ) {
149 add_action( 'flatsome_woocommerce_shop_loop_images', array( $this, 'quick_view_button' ) );
150 } elseif ( merchant_is_storefront_active() ) {
151 remove_action( 'woocommerce_before_shop_loop_item_title', 'woocommerce_template_loop_product_thumbnail', 10 );
152 add_action( 'woocommerce_before_shop_loop_item_title', function () {
153 echo '<div class="merchant-storefront-thumbnail-wrapper">';
154 woocommerce_template_loop_product_thumbnail();
155 $this->quick_view_button();
156 echo '</div>';
157 } );
158 } elseif ( merchant_is_astra_active() ) {
159 add_action( 'woocommerce_after_shop_loop_item', array( $this, 'quick_view_button' ), 7 );
160 } else {
161 add_action( 'woocommerce_after_shop_loop_item', array( $this, 'quick_view_button' ) );
162 }
163 }
164 }
165
166 // Inject quick view modal output on footer.
167 add_action( 'wp_footer', array( $this, 'modal_output' ) );
168
169 // Show Suggested Module
170 $suggested_placement = Merchant_Admin_Options::get( self::MODULE_ID, 'suggested_products_placement', 'after_add_to_cart' );
171 add_action( 'merchant_quick_view_' . $suggested_placement, array( $this, 'render_suggested_module_content' ), 10, 2 );
172
173 // Show Buy Now Module
174 add_action( 'woocommerce_after_add_to_cart_button', array( $this, 'render_buy_now_button' ) );
175
176 // Custom CSS.
177 add_filter( 'merchant_custom_css', array( $this, 'frontend_custom_css' ) );
178
179 // Modal content ajax callback.
180 add_action( 'wp_ajax_merchant_quick_view_content', array( $this, 'modal_content_ajax_callback' ) );
181 add_action( 'wp_ajax_nopriv_merchant_quick_view_content', array( $this, 'modal_content_ajax_callback' ) );
182
183 // Initialize AJAX add to cart handler (separate class).
184 $this->init_ajax_add_to_cart();
185 }
186
187 /**
188 * Initialize AJAX add to cart functionality.
189 *
190 * @return void
191 */
192 private function init_ajax_add_to_cart() {
193 Merchant_Quick_View_Ajax_Add_To_Cart::get_instance()->init();
194 }
195
196 /**
197 * Singleton
198 *
199 * @return self|null
200 */
201 public static function get_instance() {
202 if ( self::$instance === null ) {
203 self::$instance = new self();
204 }
205
206 return self::$instance;
207 }
208
209 /**
210 * Init translations.
211 *
212 * @return void
213 */
214 public function init_translations() {
215 $settings = $this->get_module_settings();
216 if ( ! empty( $settings['button_text'] ) ) {
217 Merchant_Translator::register_string( $settings['button_text'], esc_html__( 'Quick view button text', 'merchant' ) );
218 }
219 }
220
221 /**
222 * Print shortcode content.
223 *
224 * @return string
225 */
226 public function shortcode_handler() {
227 // Check if module is active.
228 if ( ! Merchant_Modules::is_module_active( $this->module_id ) ) {
229 return '';
230 }
231
232 // Check if shortcode is enabled.
233 if ( ! $this->is_shortcode_enabled() ) {
234 return '';
235 }
236
237 global $product;
238
239 $product_id = is_object( $product ) ? $product->get_id() : get_the_ID();
240
241 ob_start();
242 $this->quick_view_button( 'shortcode' );
243 $shortcode_content = ob_get_clean();
244
245 /**
246 * Filter the shortcode html content.
247 *
248 * @param string $shortcode_content shortcode html content
249 * @param string $module_id module id
250 * @param int $post_id product id
251 *
252 * @since 1.8
253 */
254 return apply_filters( 'merchant_module_shortcode_content_html', $shortcode_content, $this->module_id, $product_id );
255 }
256
257 /**
258 * Concatenate the quick view button with the content start wrap.
259 *
260 * @return string
261 */
262 public function add_quick_view_button() {
263 return $this->quick_view_button() . '<div class="product-details content-bg entry-content-wrap">';
264 }
265
266 /**
267 * Admin enqueue CSS.
268 *
269 * @return void
270 */
271 public function admin_enqueue_css() {
272 $page = ( ! empty( $_GET['page'] ) ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
273 $module = ( ! empty( $_GET['module'] ) ) ? sanitize_text_field( wp_unslash( $_GET['module'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
274
275 if ( 'merchant' === $page && self::MODULE_ID === $module ) {
276 wp_enqueue_style( 'merchant-' . self::MODULE_ID, MERCHANT_URI . 'assets/css/modules/' . self::MODULE_ID . '/quick-view.min.css', array(), MERCHANT_VERSION );
277 wp_enqueue_style( 'merchant-admin-' . self::MODULE_ID, MERCHANT_URI . 'assets/css/modules/' . self::MODULE_ID . '/admin/preview.min.css', array(), MERCHANT_VERSION );
278 }
279 }
280
281 /**
282 * Enqueue CSS.
283 *
284 * @return void
285 */
286 public function enqueue_css() {
287
288 // Specific module styles.
289 wp_enqueue_style( 'merchant-' . self::MODULE_ID, MERCHANT_URI . 'assets/css/modules/' . self::MODULE_ID . '/quick-view.min.css', array(), MERCHANT_VERSION );
290 }
291
292 /**
293 * Enqueue scripts.
294 *
295 * @return void
296 */
297 public function enqueue_scripts() {
298 if ( defined( 'WC_VERSION' ) && version_compare( WC_VERSION, '10.3', '>=' ) ) {
299 wp_enqueue_script( 'wc-zoom' );
300 wp_enqueue_script( 'wc-flexslider' );
301 } else {
302 wp_enqueue_script( 'zoom' );
303 wp_enqueue_script( 'flexslider' );
304 }
305 wp_enqueue_script( 'wc-single-product' );
306 wp_enqueue_script( 'wc-add-to-cart-variation' );
307
308 // Register and enqueue the main module script.
309 wp_enqueue_script( 'merchant-' . self::MODULE_ID, MERCHANT_URI . 'assets/js/modules/' . self::MODULE_ID . '/quick-view.min.js', array(), MERCHANT_VERSION, true );
310 }
311
312 /**
313 * Localize script with module settings.
314 *
315 * @param array $setting The merchant global object setting parameter.
316 * @return array $setting The merchant global object setting parameter.
317 */
318 public function localize_script( $setting ) {
319 $module_settings = $this->get_module_settings();
320
321 $setting[ 'quick_view' ] = true;
322 $setting[ 'quick_view_zoom' ] = $module_settings[ 'zoom_effect' ];
323 $setting[ 'ajax_add_to_cart' ] = ! empty( $module_settings[ 'ajax_add_to_cart' ] );
324
325 return $setting;
326 }
327
328 /**
329 * Enqueue botiga theme scripts.
330 *
331 * @return void
332 */
333 public function modal_compatibility_with_botiga_theme() {
334 if ( ! wp_script_is( 'botiga-product-swatch' ) ) {
335 wp_enqueue_script( 'botiga-product-swatch', BOTIGA_PRO_URI . 'assets/js/botiga-product-swatch.min.js', array(), BOTIGA_PRO_VERSION, true );
336 }
337
338 if ( ! wp_script_is( 'botiga-checkout-quantity-input' ) ) {
339 wp_enqueue_script( 'botiga-checkout-quantity-input', BOTIGA_PRO_URI . 'assets/js/botiga-checkout-quantity-input.min.js', array( 'jquery' ), BOTIGA_PRO_VERSION, true );
340 }
341 }
342
343 /**
344 * Render admin preview
345 *
346 * @param Merchant_Admin_Preview $preview
347 * @param string $module
348 *
349 * @return Merchant_Admin_Preview
350 */
351 public function render_admin_preview( $preview, $module ) {
352 if ( self::MODULE_ID === $module ) {
353 ob_start();
354 self::admin_preview_content();
355 $content = ob_get_clean();
356
357 // HTML.
358 $preview->set_html( $content );
359
360 // Button Type.
361 $preview->set_class( 'button_type', '.merchant-quick-view-button', array( 'icon', 'icon-text', 'text' ) );
362
363 // Button Text.
364 $preview->set_text( 'button_text', '.button-text' );
365
366 // Button Icon.
367 $preview->set_svg_icon( 'button_icon', '.quick-view-icon' );
368
369 // Button Position.
370 $preview->set_class( 'button_position', '.merchant-quick-view-preview', array( 'before', 'after', 'overlay' ) );
371
372 }
373
374 return $preview;
375 }
376
377 /**
378 * Admin preview content.
379 *
380 * @return void
381 */
382 public function admin_preview_content() {
383 $settings = $this->get_module_settings();
384
385 ?>
386
387 <div class="merchant-quick-view-preview <?php echo esc_attr( $settings[ 'button_position' ] ); ?>">
388 <div class="image-wrapper">
389 <div class="button-position button-position-overlay">
390 <?php $this->admin_preview_quick_view_button(); ?>
391 </div>
392 </div>
393 <h3><?php echo esc_html__( 'Product Title', 'merchant' ); ?></h3>
394 <p><?php echo esc_html__( 'The product description normally is displayed here.', 'merchant' ); ?></p>
395 <div class="button-position button-position-before">
396 <?php $this->admin_preview_quick_view_button(); ?>
397 </div>
398 <div>
399 <a href="#" class="add_to_cart_button"><?php echo esc_html__( 'Add To Cart', 'merchant' ); ?></a>
400 </div>
401 <div class="button-position button-position-after">
402 <?php $this->admin_preview_quick_view_button(); ?>
403 </div>
404
405 </div>
406
407 <?php
408 }
409
410 /**
411 * Admin preview quick view button.
412 *
413 * @return void
414 */
415 public function admin_preview_quick_view_button() {
416 $settings = $this->get_module_settings();
417
418 ?>
419
420 <a href="#" class="merchant-quick-view-button <?php echo esc_attr( $settings[ 'button_type' ] ); ?>">
421 <span class="button-type button-type-icon">
422 <span class="quick-view-icon">
423 <?php echo wp_kses( Merchant_SVG_Icons::get_svg_icon( $settings[ 'button_icon' ] ), merchant_kses_allowed_tags( array(), false ) ); ?>
424 </span>
425 </span>
426 <span class="button-type button-type-icon-text">
427 <span class="quick-view-icon">
428 <?php echo wp_kses( Merchant_SVG_Icons::get_svg_icon( $settings[ 'button_icon' ] ), merchant_kses_allowed_tags( array(), false ) ); ?>
429 </span>
430 <span class="button-text"><?php echo esc_html( $settings[ 'button_text' ] ); ?></span>
431 </span>
432 <span class="button-type button-type-text">
433 <span class="button-text"><?php echo esc_html( $settings[ 'button_text' ] ); ?></span>
434 </span>
435 </a>
436
437 <?php
438 }
439
440 /**
441 * Modal content ajax callback.
442 * TODO: Render the output through templates files.
443 *
444 * @return void
445 */
446 public function modal_content_ajax_callback() {
447 check_ajax_referer( 'merchant-nonce', 'nonce' );
448
449 if ( ! isset( $_POST['product_id'] ) || ! function_exists( 'wc_get_product' ) ) {
450 wp_send_json_error();
451 }
452
453 $args = array(
454 'product_id' => absint( $_POST['product_id'] ),
455 );
456
457 global $product;
458
459 $settings = $this->get_module_settings();
460 $product = wc_get_product( $args['product_id'] );
461
462 if ( is_wp_error( $product ) || empty( $product ) ) {
463 wp_send_json_error();
464 }
465
466 $product_id = $product->get_id();
467 $hide_quantity = ( empty( $settings[ 'show_quantity' ] ) ) ? 'merchant-hide-quantity' : '';
468
469 ob_start();
470 ?>
471 <div id="product-<?php echo absint( $product_id ); ?>" <?php wc_product_class( '', $product ); ?>>
472 <div class="merchant-quick-view-row">
473 <div class="merchant-quick-view-column">
474 <div class="merchant-quick-view-product-gallery">
475 <?php woocommerce_show_product_images(); ?>
476 </div>
477 </div>
478
479 <div class="merchant-quick-view-column">
480 <div class="merchant-quick-view-summary product-gallery-summary">
481 <div class="merchant-quick-view-product-title">
482 <h2 class="product_title entry-title"><?php echo esc_html( $product->get_title() ); ?></h2>
483 </div>
484
485 <?php if ( 0 !== $product->get_average_rating() ) : ?>
486 <div class="merchant-quick-view-product-rating">
487 <div class="woocommerce-product-rating">
488 <?php echo wp_kses( wc_get_rating_html( $product->get_average_rating() ), merchant_kses_allowed_tags() ); ?>
489 </div>
490 </div>
491 <?php endif; ?>
492
493 <div class="merchant-quick-view-product-price">
494 <div class="price"><?php echo wp_kses( $product->get_price_html(), merchant_kses_allowed_tags() ); ?></div>
495 </div>
496
497 <?php if ( 'top' === $settings[ 'place_product_description' ] ) : ?>
498 <?php
499 /**
500 * Hook 'merchant_quick_view_before_product_description'
501 *
502 * @since 1.9.14
503 */
504 do_action( 'merchant_quick_view_before_product_description' );
505
506 $description = $settings[ 'description_style' ] === 'full' ? $product->get_description() : $product->get_short_description();
507
508 /**
509 * `merchant_quick_view_description`
510 *
511 * @since 1.9.16
512 */
513 $description = apply_filters( 'merchant_quick_view_description', $description );
514 ?>
515 <div class="merchant-quick-view-product-excerpt">
516 <?php echo wp_kses_post( do_shortcode( $description ) ); ?>
517 </div>
518
519 <?php
520 /**
521 * Hook 'merchant_quick_view_before_product_description'
522 *
523 * @since 1.9.14
524 */
525 do_action( 'merchant_quick_view_after_product_description' );
526 ?>
527 <?php endif; ?>
528
529 <?php
530 /**
531 * Hook 'merchant_quick_view_before_add_to_cart'
532 *
533 * @since 1.9.14
534 */
535 do_action( 'merchant_quick_view_before_add_to_cart', $product, $settings );
536 ?>
537 <div class="merchant-quick-view-product-add-to-cart <?php echo esc_attr( $hide_quantity ); ?>">
538 <?php woocommerce_template_single_add_to_cart(); ?>
539 </div>
540
541 <?php
542 /**
543 * Hook 'merchant_quick_view_after_add_to_cart'
544 *
545 * @since 1.9.14
546 */
547 do_action( 'merchant_quick_view_after_add_to_cart', $product, $settings );
548 ?>
549
550 <?php if ( 'bottom' === $settings[ 'place_product_description' ] ) : ?>
551 <?php
552 /**
553 * Hook 'merchant_quick_view_before_product_description'
554 *
555 * @since 1.9.14
556 */
557 do_action( 'merchant_quick_view_before_product_description' );
558
559 $description = $settings[ 'description_style' ] === 'full' ? $product->get_description() : $product->get_short_description();
560
561 /**
562 * `merchant_quick_view_description`
563 *
564 * @since 1.9.16
565 */
566 $description = apply_filters( 'merchant_quick_view_description', $description );
567 ?>
568 <div class="merchant-quick-view-product-excerpt">
569 <?php echo wp_kses_post( do_shortcode( $description ) ); ?>
570 </div>
571
572 <?php
573 /**
574 * Hook 'merchant_quick_view_after_product_description'
575 *
576 * @since 1.9.14
577 */
578 do_action( 'merchant_quick_view_after_product_description' );
579 ?>
580 <?php endif; ?>
581 <div class="merchant-quick-view-product-meta">
582 <?php woocommerce_template_single_meta(); ?>
583 </div>
584
585 <?php
586 /**
587 * Hook 'merchant_quick_view_after_product_excerpt'
588 *
589 * @since 1.0.0
590 */
591 do_action( 'merchant_quick_view_after_product_meta' );
592 ?>
593 </div>
594 </div>
595 </div>
596 </div>
597 <?php
598 $content = ob_get_contents();
599
600 ob_get_clean();
601
602 wp_send_json_success( $content );
603 }
604
605
606 /**
607 * Modal output.
608 * TODO: Render the output through templates files.
609 *
610 * @return void
611 */
612 public function modal_output() {
613 $settings = $this->get_module_settings();
614 ?>
615 <div class="single-product merchant-quick-view-modal merchant-quick-view-<?php echo esc_attr( $settings[ 'place_product_image' ] ); ?>">
616 <div class="merchant-quick-view-overlay"></div>
617 <div class="merchant-quick-view-loader">
618 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
619 <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" />
620 <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" />
621 </svg>
622 </div>
623 <div class="merchant-quick-view-inner">
624 <a href="#" class="merchant-quick-view-close-button" title="<?php echo esc_attr__( 'Close quick view modal', 'merchant' ); ?>">
625 <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512">
626 <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"/>
627 </svg>
628 </a>
629 <div class="merchant-quick-view-content"></div>
630 </div>
631 </div>
632 <?php
633 }
634
635 /**
636 * Quick view button.
637 *
638 * @return void
639 */
640 public function quick_view_button( $context = '' ) {
641 global $product;
642
643 if ( ! is_object( $product ) ) {
644 return;
645 }
646
647 $settings = $this->get_module_settings();
648 $product_id = $product->get_id();
649
650 $button_text_html = '';
651 $button_icon_html = '';
652
653 if ( 'icon' === $settings[ 'button_type' ] || 'icon-text' === $settings[ 'button_type' ] ) {
654 $button_icon_html = Merchant_SVG_Icons::get_svg_icon( $settings[ 'button_icon' ] );
655 }
656
657 if ( 'text' === $settings[ 'button_type' ] || 'icon-text' === $settings[ 'button_type' ] ) {
658 $button_text_html = '<span>' . Merchant_Translator::translate( $settings[ 'button_text' ] ) . '</span>';
659 }
660
661 $classes = 'button wp-element-button merchant-quick-view-button';
662 $classes .= $context !== 'shortcode' ? ' merchant-quick-view-position-' . ( $settings[ 'button_position' ] ?? '' ) : '';
663 $classes .= ( $context !== 'shortcode' && ! empty( $settings['mobile_position'] ) ) ? ' merchant-quick-view-position-has-mobile-position' : '';
664 ?>
665 <button class="<?php echo esc_attr( $classes ); ?>" data-product-id="<?php echo absint( $product_id ); ?>" type="button">
666 <?php echo wp_kses( $button_icon_html, merchant_kses_allowed_tags( array(), false ) ) . wp_kses_post( $button_text_html ); ?>
667 </button>
668 <?php
669 }
670
671 /**
672 * Renders the Buy Now button.
673 *
674 * @return void
675 */
676 public function render_buy_now_button() {
677 if ( ! Merchant_Modules::is_module_active( Merchant_Buy_Now::MODULE_ID ) ) {
678 return;
679 }
680
681 // Don't include on Single Product
682 if ( ! did_action( 'merchant_quick_view_before_add_to_cart' ) ) {
683 return;
684 }
685
686 $show_buy_now = (bool) Merchant_Admin_Options::get( $this->module_id, 'show_buy_now_button', false );
687 if ( ! $show_buy_now ) {
688 return;
689 }
690
691 global $product;
692
693 /**
694 * Filters whether the Buy Now button should be excluded for a specific product in Quick View.
695 *
696 * This filter allows modules (like Buy Now) to control the visibility of the Buy Now button
697 * in the Quick View modal based on their own exclusion rules.
698 *
699 * @param bool $is_excluded Whether the product is excluded. Default false.
700 * @param WC_Product $product The product object.
701 *
702 * @since 2.2.4
703 */
704 if ( apply_filters( 'merchant_buy_now_is_excluded', false, $product ) ) {
705 return;
706 }
707
708 $text = Merchant_Admin_Options::get( Merchant_Buy_Now::MODULE_ID, 'button-text', esc_html__( 'Buy Now', 'merchant' ) );
709
710 $_wrapper_classes = array();
711 $_wrapper_classes[] = $product->get_type() === 'variable' ? 'disabled' : '';
712
713 /**
714 * Hook 'merchant_module_buy_now_wrapper_class'
715 *
716 * @since 1.8
717 */
718 $wrapper_classes = apply_filters( 'merchant_module_buy_now_wrapper_class', $_wrapper_classes );
719 ?>
720 <!-- 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". -->
721 <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>
722 <?php
723 }
724
725 /**
726 * Renders the suggested module content based on the provided settings.
727 *
728 * @param $product
729 * @param $settings
730 *
731 * @return void
732 */
733 public function render_suggested_module_content( $product, $settings ) {
734 $show_suggested = (bool) Merchant_Admin_Options::get( $this->module_id, 'show_suggested_products', true );
735
736 if ( ! $show_suggested || empty( $product ) ) {
737 return;
738 }
739
740 $suggested_module = $settings['suggested_products_module'] ?? 'bulk_discounts';
741
742 switch ( $suggested_module ) {
743 case 'bulk_discounts':
744 $this->print_bulk_discounts( $product );
745 break;
746
747 case 'buy_x_get_y':
748 $this->print_buy_x_get_y( $product );
749 break;
750
751 case 'frequently_bought_together':
752 $this->print_frequently_bought_together( $product );
753 break;
754 }
755 }
756
757 /**
758 * Prints the bulk discounts for the specified product.
759 *
760 * @param $product
761 *
762 * @return void
763 */
764 public function print_bulk_discounts( $product ) {
765 if ( ! merchant_is_pro_active() || ! Merchant_Modules::is_module_active( Merchant_Volume_Discounts::MODULE_ID ) ) {
766 return;
767 }
768
769 $product_id = $product->get_id();
770 $discount_tiers = Merchant_Pro_Volume_Discounts::availabe_offers( $product_id );
771
772 if ( ! empty( $discount_tiers ) ) {
773 merchant_get_template_part(
774 Merchant_Volume_Discounts::MODULE_TEMPLATES_PATH,
775 'single-product',
776 array(
777 'settings' => Merchant_Admin_Options::get_all( Merchant_Volume_Discounts::MODULE_ID ),
778 'product' => $product,
779 'discount_tiers' => $discount_tiers,
780 'product_price' => $product->get_price(),
781 'in_cart' => Merchant_Pro_Volume_Discounts::is_in_cart( $product_id ),
782 'product_cart_quantity' => Merchant_Pro_Volume_Discounts::get_product_cart_quantity( $product_id ),
783 'product_id' => $product_id,
784 )
785 );
786 }
787 }
788
789 /**
790 * Prints the Buy X Get Y offers for the specified product.
791 *
792 * @param $product
793 *
794 * @return void
795 */
796 public function print_buy_x_get_y( $product ) {
797 if ( ! merchant_is_pro_active() || ! Merchant_Modules::is_module_active( Merchant_Buy_X_Get_Y::MODULE_ID ) ) {
798 return;
799 }
800
801 $product_id = $product->get_id();
802 $offers = Merchant_Pro_Buy_X_Get_Y::availabe_offers( $product_id );
803
804 if ( ! empty( $offers ) ) {
805 merchant_get_template_part(
806 Merchant_Buy_X_Get_Y::MODULE_TEMPLATES,
807 'single-product',
808 array(
809 'offers' => $offers,
810 'nonce' => wp_create_nonce( 'merchant_bogo_add_to_cart' ),
811 'settings' => Merchant_Admin_Options::get_all( Merchant_Buy_X_Get_Y::MODULE_ID ),
812 'product' => $product_id,
813 )
814 );
815 }
816 }
817
818 /**
819 * Prints the frequently bought together bundles for the specified product.
820 *
821 * @param $product
822 *
823 * @return void
824 */
825 public function print_frequently_bought_together( $product ) {
826 if ( ! merchant_is_pro_active() || ! Merchant_Modules::is_module_active( Merchant_Frequently_Bought_Together::MODULE_ID ) ) {
827 return;
828 }
829
830 $post_id = $product->get_id();
831 $bundle_data = Merchant_Pro_Frequently_Bought_Together::availabe_offers( $post_id, Merchant_Pro_Frequently_Bought_Together::bundles() );
832
833 if ( ! empty( $bundle_data ) ) {
834 $bundles[ $post_id ] = Merchant_Pro_Frequently_Bought_Together::map_bundles( $bundle_data );
835 }
836
837 if ( ! empty( $bundles ) ) {
838 merchant_get_template_part(
839 Merchant_Frequently_Bought_Together::MODULE_TEMPLATES_PATH,
840 'single-product',
841 array(
842 'bundles' => $bundles,
843 'nonce' => wp_create_nonce( Merchant_Pro_Frequently_Bought_Together::AJAX_NONCE_ACTION ),
844 'settings' => Merchant_Admin_Options::get_all( Merchant_Frequently_Bought_Together::MODULE_ID ),
845 )
846 );
847 }
848 }
849
850 /**
851 * Custom CSS.
852 *
853 * @return string
854 */
855 public function get_module_custom_css() {
856 $css = '';
857
858 // Button Icon Color.
859 $css .= Merchant_Custom_CSS::get_variable_css( 'quick-view', 'icon-color', '#ffffff', '.merchant-quick-view-button', '--mrc-qv-button-icon-color' );
860
861 // Button Icon Color (hover).
862 $css .= Merchant_Custom_CSS::get_variable_css( 'quick-view', 'icon-hover-color', '#ffffff', '.merchant-quick-view-button', '--mrc-qv-button-icon-color-hover' );
863
864 // Button Text Color.
865 $css .= Merchant_Custom_CSS::get_variable_css( 'quick-view', 'text-color', '#ffffff', '.merchant-quick-view-button', '--mrc-qv-button-text-color' );
866
867 // Button Text Color (hover).
868 $css .= Merchant_Custom_CSS::get_variable_css( 'quick-view', 'text-hover-color', '#ffffff', '.merchant-quick-view-button', '--mrc-qv-button-text-color-hover' );
869
870 // Button Border Color.
871 $css .= Merchant_Custom_CSS::get_variable_css( 'quick-view', 'border-color', '#212121', '.merchant-quick-view-button', '--mrc-qv-button-border-color' );
872
873 // Button Border Color (hover).
874 $css .= Merchant_Custom_CSS::get_variable_css( 'quick-view', 'border-hover-color', '#414141', '.merchant-quick-view-button', '--mrc-qv-button-border-color-hover' );
875
876 // Button Background Color.
877 $css .= Merchant_Custom_CSS::get_variable_css( 'quick-view', 'background-color', '#212121', '.merchant-quick-view-button', '--mrc-qv-button-bg-color' );
878
879 // Button Background Color (hover).
880 $css .= Merchant_Custom_CSS::get_variable_css( 'quick-view', 'background-hover-color', '#414141', '.merchant-quick-view-button', '--mrc-qv-button-bg-color-hover' );
881
882 // Button Position Top.
883 $css .= Merchant_Custom_CSS::get_variable_css( 'quick-view', 'button-position-top', '50', '.merchant-quick-view-button', '--mrc-qv-button-position-top', '%' );
884
885 // Button Position Top (Mobile).
886 $css .= Merchant_Custom_CSS::get_variable_css( 'quick-view', 'button-position-top-mobile', '50', '.merchant-quick-view-button', '--mrc-qv-button-position-top-mobile', '%' );
887
888 // Button Position Left.
889 $css .= Merchant_Custom_CSS::get_variable_css( 'quick-view', 'button-position-left', '50', '.merchant-quick-view-button', '--mrc-qv-button-position-left', '%' );
890
891 // Button Position Left (Mobile).
892 $css .= Merchant_Custom_CSS::get_variable_css( 'quick-view', 'button-position-left-mobile', '50', '.merchant-quick-view-button', '--mrc-qv-button-position-left-mobile', '%' );
893
894 // Modal Width.
895 $css .= Merchant_Custom_CSS::get_variable_css( 'quick-view', 'modal_width', 1000, '.merchant-quick-view-modal', '--mrc-qv-modal-width', 'px' );
896
897 // Modal Height.
898 $css .= Merchant_Custom_CSS::get_variable_css( 'quick-view', 'modal_height', 500, '.merchant-quick-view-modal', '--mrc-qv-modal-height', 'px' );
899
900 // Sale Price Color.
901 $css .= Merchant_Custom_CSS::get_variable_css( 'quick-view', 'sale-price-color', '#212121', '.merchant-quick-view-modal', '--mrc-qv-modal-sale-price-color' );
902
903 // Regular Price Color.
904 $css .= Merchant_Custom_CSS::get_variable_css( 'quick-view', 'regular-price-color', '#414141', '.merchant-quick-view-modal', '--mrc-qv-modal-regular-price-color' );
905
906 return $css;
907 }
908
909 /**
910 * Admin custom CSS.
911 *
912 * @param string $css The custom CSS.
913 * @return string $css The custom CSS.
914 */
915 public function admin_custom_css( $css ) {
916 $css .= $this->get_module_custom_css();
917
918 return $css;
919 }
920
921 /**
922 * Frontend custom CSS.
923 *
924 * @param string $css The custom CSS.
925 * @return string $css The custom CSS.
926 */
927 public function frontend_custom_css( $css ) {
928 $css .= $this->get_module_custom_css();
929
930 $theme = wp_get_theme();
931 $theme_name = $theme->get( 'Name' );
932
933 if ( 'Astra' === $theme_name ) {
934 $css .= '
935 .astra-shop-thumbnail-wrap {
936 position: relative;
937 }
938 .astra-shop-thumbnail-wrap img {
939 width: 100%;
940 }
941 ';
942 }
943
944 if ( 'Storefront' === $theme_name ) {
945 $css .= '
946 .merchant-storefront-thumbnail-wrapper {
947 position: relative;
948 }
949 ';
950 }
951
952 return $css;
953 }
954 }
955
956 // Initialize the module.
957 add_action( 'init', function() {
958 // Load AJAX add to cart class file.
959 require_once MERCHANT_DIR . 'inc/modules/quick-view/class-quick-view-ajax-add-to-cart.php';
960
961 // Create and initialize the module.
962 Merchant_Modules::create_module( Merchant_Quick_View::get_instance() );
963 } );
964