PluginProbe
Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant / 1.8
Product Labels, Quick View, Buy Now, Pre-Orders, Frequently Bought Together & More for WooCommerce – Merchant v1.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 / product-labels / class-product-labels.php

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

645 lines 19.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Product Labels.
5 *
6 * @package Merchant
7 */
8
9 if ( ! defined( 'ABSPATH' ) ) {
10 exit; // Exit if accessed directly
11 }
12
13 /**
14 * Product Labels Class.
15 *
16 */
17 class Merchant_Product_Labels extends Merchant_Add_Module {
18
19 /**
20 * Module ID.
21 *
22 */
23 const MODULE_ID = 'product-labels';
24
25 /**
26 * Is module preview.
27 *
28 */
29 public static $is_module_preview = false;
30
31 /**
32 * Constructor.
33 *
34 */
35 public function __construct() {
36 // Module id.
37 $this->module_id = self::MODULE_ID;
38
39 // WooCommerce only.
40 $this->wc_only = true;
41
42 // Parent construct.
43 parent::__construct();
44
45 // Module section.
46 $this->module_section = 'convert-more';
47
48 // Module default settings.
49 $this->module_default_settings = array(
50 'label_text' => __( 'Spring Special', 'merchant' ),
51 'display_percentage' => 0,
52 'percentage_text' => '-{value}%',
53 'label_position' => 'top-left',
54 'label_shape' => 0,
55 'label_text_transform' => 'uppercase',
56 );
57
58 // Mount preview url.
59 $preview_url = site_url( '/' );
60
61 if ( function_exists( 'wc_get_page_id' ) ) {
62 $preview_url = get_permalink( wc_get_page_id( 'shop' ) );
63 }
64
65 // Module data.
66 $this->module_data = Merchant_Admin_Modules::$modules_data[ self::MODULE_ID ];
67 $this->module_data['preview_url'] = $preview_url;
68
69 // Module options path.
70 $this->module_options_path = MERCHANT_DIR . 'inc/modules/' . self::MODULE_ID . '/admin/options.php';
71
72 // Is module preview page.
73 if ( is_admin() && parent::is_module_settings_page() ) {
74 self::$is_module_preview = true;
75
76 // Enqueue admin styles.
77 add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_css' ) );
78 add_action( 'admin_enqueue_scripts', array( $this, 'admin_enqueue_js' ) );
79
80 // Admin preview box.
81 add_filter( 'merchant_module_preview', array( $this, 'render_admin_preview' ), 10, 2 );
82
83 // Custom CSS.
84 // The custom CSS should be added here as well due to ensure preview box works properly.
85 add_filter( 'merchant_custom_css', array( $this, 'admin_custom_css' ) );
86 }
87
88 if ( Merchant_Modules::is_module_active( self::MODULE_ID ) && is_admin() ) {
89 // Init translations.
90 $this->init_translations();
91 }
92
93 if ( ! Merchant_Modules::is_module_active( self::MODULE_ID ) ) {
94 return;
95 }
96
97 // Return early if it's on admin but not in the respective module settings page.
98 if ( is_admin() && ! parent::is_module_settings_page() ) {
99 return;
100 }
101
102 // Enqueue styles.
103 add_action( 'merchant_enqueue_before_main_css_js', array( $this, 'enqueue_css' ) );
104
105 // Inject module content in the products.
106 add_action( 'woocommerce_before_shop_loop_item_title', array( $this, 'loop_product_output' ) );
107 add_action( 'woocommerce_product_thumbnails', array( $this, 'single_product_output' ) );
108 add_action( 'woostify_product_images_box_end', array( $this, 'single_product_output' ) );
109 add_filter( 'woocommerce_blocks_product_grid_item_html', array( $this, 'products_block' ), 10, 3 );
110
111 // Custom CSS.
112 add_filter( 'merchant_custom_css', array( $this, 'frontend_custom_css' ) );
113 }
114
115 /**
116 * Init translations.
117 *
118 * @return void
119 */
120 public function init_translations() {
121 $settings = $this->get_module_settings();
122 if ( isset( $settings['labels'] ) ) {
123 foreach ( $settings['labels'] as $label ) {
124 if ( isset( $label['label'] ) ) {
125 Merchant_Translator::register_string( $label['label'], esc_html__( 'Product Labels', 'merchant' ) );
126 }
127 if ( isset( $label['percentage_text'] ) ) {
128 Merchant_Translator::register_string( $label['percentage_text'], esc_html__( 'Percentage text in product labels', 'merchant' ) );
129 }
130 }
131 }
132 }
133
134 /**
135 * Function for `woocommerce_blocks_product_grid_item_html` filter-hook.
136 *
137 * @param string $html Product grid item HTML.
138 * @param array $data Product data passed to the template.
139 * @param \WC_Product $product Product object.
140 *
141 * @return string
142 */
143 public function products_block( $html, $data, $product ) {
144 return str_replace( '</li>', $this->get_labels( $product, 'archive' ) . '</li>', $html );
145 }
146
147 /**
148 * Admin enqueue CSS.
149 *
150 * @return void
151 */
152 public function admin_enqueue_css() {
153 $page = ( ! empty( $_GET['page'] ) ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
154 $module = ( ! empty( $_GET['module'] ) ) ? sanitize_text_field( wp_unslash( $_GET['module'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
155
156 if ( 'merchant' === $page && self::MODULE_ID === $module ) {
157 wp_enqueue_style( 'merchant-admin-' . self::MODULE_ID, MERCHANT_URI . 'assets/css/modules/' . self::MODULE_ID . '/admin/preview.min.css', array(), MERCHANT_VERSION );
158 }
159 }
160
161 /**
162 * Admin enqueue CSS.
163 *
164 * @return void
165 */
166 public function admin_enqueue_js() {
167 $page = ( ! empty( $_GET['page'] ) ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
168 $module = ( ! empty( $_GET['module'] ) ) ? sanitize_text_field( wp_unslash( $_GET['module'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
169
170 if ( 'merchant' === $page && self::MODULE_ID === $module ) {
171 wp_enqueue_script( 'merchant-' . self::MODULE_ID, MERCHANT_URI . 'assets/js/modules/product-labels/admin/preview.min.js', array( 'jquery', 'merchant-admin' ), '4.0.13',
172 true );
173 }
174 }
175
176 /**
177 * Enqueue CSS.
178 *
179 * @return void
180 */
181 public function enqueue_css() {
182 // Specific module styles.
183 wp_enqueue_style( 'merchant-' . self::MODULE_ID, MERCHANT_URI . 'assets/css/modules/' . self::MODULE_ID . '/product-labels.min.css', array(), MERCHANT_VERSION );
184 }
185
186 /**
187 * Render admin preview
188 *
189 * @param Merchant_Admin_Preview $preview
190 * @param string $module
191 *
192 * @return Merchant_Admin_Preview
193 */
194 public function render_admin_preview( $preview, $module ) {
195 if ( self::MODULE_ID === $module ) {
196 ob_start();
197 self::admin_preview_content();
198 $content = ob_get_clean();
199
200 // HTML.
201 $preview->set_html( $content );
202
203 // Label Text.
204 $preview->set_text( 'label_text', '.merchant-onsale' );
205
206 // Position.
207 $preview->set_class( 'label_position', '.merchant-onsale', array( 'top-left', 'top-right' ) );
208 }
209
210 return $preview;
211 }
212
213 /**
214 * Admin preview content.
215 *
216 * @return void
217 */
218 public function admin_preview_content() {
219 $settings = $this->get_module_settings();
220
221 $label_text = ! empty( $settings['display_percentage'] ) ? str_replace( '{value}', 20, $settings['percentage_text'] ) : $settings['label_text'];
222
223 ?>
224
225 <div class="merchant-product-labels-preview">
226 <div class="image-wrapper">
227 <span class="merchant-label merchant-onsale-<?php
228 echo esc_attr( $settings['label_position'] ); ?> merchant-onsale-shape-<?php
229 echo esc_attr( $settings['label_shape'] ) ?>"><?php
230 echo esc_html( $label_text ); ?></span>
231 </div>
232 <h3><?php
233 echo esc_html__( 'Product Title', 'merchant' ); ?></h3>
234 <p><?php
235 echo esc_html__( 'The product description normally goes here.', 'merchant' ); ?></p>
236 </div>
237
238 <?php
239 }
240
241 /**
242 * Calculate the percentage and display it replacing the label text.
243 *
244 * @return string label
245 */
246 public function percentage_label( $product, $label ) {
247 $label_text = isset( $label['label'] ) ? $label['label'] : '';
248 if ( ! empty( $label['percentage_text'] ) ) {
249 if ( $product->is_type( 'variable' ) ) {
250 $percentages = array();
251 $prices = $product->get_variation_prices();
252
253 foreach ( $prices['price'] as $key => $price ) {
254 if ( $prices['regular_price'][ $key ] !== $price ) {
255 $percentages[] = round( 100 - ( floatval( $prices['sale_price'][ $key ] ) / floatval( $prices['regular_price'][ $key ] ) * 100 ) );
256 }
257 }
258
259 $percentage = max( $percentages );
260 } elseif ( $product->is_type( 'grouped' ) ) {
261 $percentages = array();
262 $children_ids = $product->get_children();
263
264 foreach ( $children_ids as $child_id ) {
265 $child_product = wc_get_product( $child_id );
266 $regular_price = (float) $child_product->get_regular_price();
267 $sale_price = (float) $child_product->get_sale_price();
268
269 if ( 0 != $sale_price || ! empty( $sale_price ) ) {
270 $percentages[] = round( 100 - ( ( $sale_price / $regular_price ) * 100 ) );
271 }
272 }
273 $percentage = max( $percentages );
274 } else {
275 $regular_price = (float) $product->get_regular_price();
276 $sale_price = (float) $product->get_sale_price();
277
278 if ( 0 != $sale_price || ! empty( $sale_price ) ) {
279 $percentage = round( 100 - ( ( $sale_price / $regular_price ) * 100 ) );
280 }
281 }
282
283 $label_text = str_replace( '{value}', $percentage, Merchant_Translator::translate( $label['percentage_text'] ) );
284 }
285
286 $label['label'] = $label_text;
287
288 return $label;
289 }
290
291 /**
292 * Custom CSS.
293 *
294 * @return string
295 */
296 public function get_module_custom_css() {
297 $css = '';
298
299 // Border Radius.
300 $css .= Merchant_Custom_CSS::get_variable_css( 'product-labels', 'label_shape', 0, '.merchant-label', '--mrc-pl-border-radius', 'px' );
301
302 // Text Transform.
303 $css .= Merchant_Custom_CSS::get_variable_css( 'product-labels', 'label_text_transform', 'uppercase', '.merchant-label', '--mrc-pl-text-transform' );
304
305 // Padding.
306 $css .= Merchant_Custom_CSS::get_variable_css( 'product-labels', 'padding', 8, '.merchant-label', '--mrc-pl-padding', 'px' );
307
308 // Font Size.
309 $css .= Merchant_Custom_CSS::get_variable_css( 'product-labels', 'font-size', 14, '.merchant-label', '--mrc-pl-font-size', 'px' );
310
311
312 return $css;
313 }
314
315 /**
316 * Admin custom CSS.
317 *
318 * @param string $css The custom CSS.
319 *
320 * @return string $css The custom CSS.
321 */
322 public function admin_custom_css( $css ) {
323 $css .= $this->get_module_custom_css();
324
325 return $css;
326 }
327
328 /**
329 * Frontend custom CSS.
330 *
331 * @param string $css The custom CSS.
332 *
333 * @return string $css The custom CSS.
334 */
335 public function frontend_custom_css( $css ) {
336 // Append module custom CSS.
337 $css .= $this->get_module_custom_css();
338
339 // Append module custom CSS based on themes (ensure themes compatibility).
340 // Detect the current theme.
341 $theme = wp_get_theme();
342 $theme_name = $theme->get( 'Name' );
343
344 // All themes.
345 $css .= '
346 .woocommerce .onsale {
347 display: none !important;
348 }
349 ';
350
351 // Astra.
352 if ( 'Astra' === $theme_name ) {
353 $css .= '
354 .woocommerce .ast-onsale-card {
355 display: none !important;
356 }
357 ';
358 }
359
360 return $css;
361 }
362
363 /**
364 * Single product output.
365 *
366 * @return void
367 */
368 public function single_product_output() {
369 global $product;
370
371 echo wp_kses( $this->get_labels( $product, 'single' ), array(
372 'div' => array(
373 'class' => array(),
374 ),
375 'strong' => array(),
376 'span' => array(
377 'class' => array(),
378 'style' => array(),
379 ),
380 ) );
381 }
382
383 /**
384 * Loop product output.
385 *
386 * @return void
387 */
388 public function loop_product_output() {
389 global $product;
390
391 echo wp_kses( $this->get_labels( $product, 'archive' ), array(
392 'div' => array(
393 'class' => array(),
394 ),
395 'strong' => array(),
396 'span' => array(
397 'class' => array(),
398 'style' => array(),
399 ),
400 ) );
401 }
402
403 /**
404 * Get labels group.
405 *
406 * @param $product WC_Product product object.
407 * @param $context string context archive or single.
408 *
409 * @return string product labels html.
410 */
411 public function get_labels( $product, $context = 'both' ) {
412 $settings = $this->get_module_settings();
413 $product_labels_html = '';
414 if ( isset( $settings['labels'] ) ) {
415 $labels = $settings['labels'];
416 foreach ( $labels as $label ) {
417 if ( ! isset( $label['pages_to_display'] ) ) {
418 continue;
419 }
420 if ( $label['pages_to_display'] !== 'both' ) {
421 if ( $label['pages_to_display'] === 'single' && $context !== 'single' ) {
422 continue;
423 }
424 if ( $label['pages_to_display'] === 'archive' && $context !== 'archive' ) {
425 continue;
426 }
427 }
428 if ( isset( $label['display_rules'] ) ) {
429 switch ( $label['display_rules'] ) {
430 case 'featured_products':
431 if ( $this->is_featured( $product ) ) {
432 $product_labels_html .= $this->label( $label );
433 }
434 break;
435 case 'products_on_sale':
436 if ( $this->is_on_sale( $product ) ) {
437 $product_labels_html .= $this->label( $this->percentage_label( $product, $label ) );
438 }
439 break;
440 case 'by_category':
441 if ( isset( $label['product_cats'] ) && $this->is_in_category( $product, $label['product_cats'] ) ) {
442 $product_labels_html .= $this->label( $label );
443 }
444 break;
445 case 'out_of_stock':
446 if ( $this->is_out_of_stock( $product ) ) {
447 $product_labels_html .= $this->label( $label );
448 }
449 break;
450 case 'new_products':
451 if ( isset( $label['new_products_days'] ) && $this->is_new( $product, $label['new_products_days'] ) ) {
452 $product_labels_html .= $this->label( $label );
453 }
454 break;
455 }
456 }
457 }
458 } else {
459 // legacy mode support.
460 return $this->legacy_product_label( $product );
461 }
462
463 if ( $product_labels_html ) {
464 return '<div class="merchant-product-labels position-' . esc_attr( $settings['label_position'] ) . '">' . $product_labels_html . '</div>';
465 }
466
467 return '';
468 }
469
470
471 /**
472 * Product label output.
473 *
474 * @return string legacy product label html.
475 */
476 private function legacy_product_label( $product ) {
477 global $product;
478 $settings = $this->get_module_settings();
479 $styles = array();
480
481 if ( ! empty( $product ) && $this->is_on_sale( $product ) ) {
482 $label_text = $settings['label_text'];
483 if ( ! empty( $settings['display_percentage'] ) ) {
484 if ( $product->is_type( 'variable' ) ) {
485 $percentages = array();
486 $prices = $product->get_variation_prices();
487
488 foreach ( $prices['price'] as $key => $price ) {
489 if ( $prices['regular_price'][ $key ] !== $price ) {
490 $percentages[] = round( 100 - ( floatval( $prices['sale_price'][ $key ] ) / floatval( $prices['regular_price'][ $key ] ) * 100 ) );
491 }
492 }
493
494 $percentage = max( $percentages );
495 } elseif ( $product->is_type( 'grouped' ) ) {
496 $percentages = array();
497 $children_ids = $product->get_children();
498
499 foreach ( $children_ids as $child_id ) {
500 $child_product = wc_get_product( $child_id );
501 $regular_price = (float) $child_product->get_regular_price();
502 $sale_price = (float) $child_product->get_sale_price();
503
504 if ( 0 != $sale_price || ! empty( $sale_price ) ) {
505 $percentages[] = round( 100 - ( ( $sale_price / $regular_price ) * 100 ) );
506 }
507 }
508 $percentage = max( $percentages );
509 } else {
510 $regular_price = (float) $product->get_regular_price();
511 $sale_price = (float) $product->get_sale_price();
512
513 if ( 0 != $sale_price || ! empty( $sale_price ) ) {
514 $percentage = round( 100 - ( ( $sale_price / $regular_price ) * 100 ) );
515 }
516 }
517
518 $label_text = str_replace( '{value}', $percentage, Merchant_Translator::translate( $settings['percentage_text'] ) );
519 }
520
521 $styles['background-color'] = isset( $settings['background_color'] ) ? $settings['background_color'] : '#212121';
522 $styles['color'] = isset( $settings['text_color'] ) ? $settings['text_color'] : '#ffffff';
523 $styles['text-transform'] = isset( $settings['label_text_transform'] ) ? $settings['label_text_transform'] : 'uppercase';
524 $styles['padding'] = isset( $settings['padding'] ) ? $settings['padding'] . 'px' : 8 . 'px';
525 $styles['font-size'] = isset( $settings['font-size'] ) ? $settings['font-size'] . 'px' : 14 . 'px';
526 $styles['border-radius'] = isset( $settings['label_shape'] ) ? $settings['label_shape'] . 'px' : 8 . 'px';
527
528 return '<div class="merchant-product-labels position-' . esc_attr( $settings['label_position'] ) . '"><span class="merchant-label merchant-label-'
529 . esc_attr( $settings['label_position'] ) . ' merchant-onsale-shape-' . esc_attr( $settings['label_shape'] ) . '" style="' . merchant_array_to_css( $styles )
530 . '">' . esc_html( Merchant_Translator::translate( $label_text ) ) . '</span></div>';
531 }
532
533 return '';
534 }
535
536 /**
537 * Get label HTML.
538 *
539 * @return string
540 */
541 public function label( $label_data ) {
542 $label_position = Merchant_Admin_Options::get( self::MODULE_ID, 'label_position', 'top-left' );
543 $label_shape = Merchant_Admin_Options::get( self::MODULE_ID, 'label_shape', 0 );
544 $styles = array();
545 if ( ! empty( $label_data['background_color'] ) ) {
546 $styles['background-color'] = $label_data['background_color'];
547 }
548 if ( ! empty( $label_data['text_color'] ) ) {
549 $styles['color'] = $label_data['text_color'];
550 }
551 if ( empty( $label_data['label'] ) ) {
552 return '';
553 }
554 $label = '<span class="merchant-label merchant-label-' . esc_attr( $label_position ) . ' merchant-label-shape-'
555 . esc_attr( $label_shape ) . '" style="' . merchant_array_to_css( $styles ) . '">'
556 . trim( esc_html( Merchant_Translator::translate( $label_data['label'] ) ) ) . '</span>';
557
558 /**
559 * Filter the single product label.
560 *
561 * @param string $label The label HTML.
562 * @param array $label_data The label data.
563 *
564 * @since 1.6
565 */
566 return apply_filters( 'merchant_product_label', $label, $label_data );
567 }
568
569 /**
570 * Get label data.
571 *
572 * @param $product WC_Product product object.
573 *
574 * @return bool
575 */
576 private function is_on_sale( $product ) {
577 return $product !== null && $product->is_on_sale();
578 }
579
580 /**
581 * Check if product is in category.
582 *
583 * @param $product WC_Product product object.
584 * @param $slug string category slug.
585 *
586 * @return bool
587 */
588 private function is_in_category( $product, $slug ) {
589 $terms = get_the_terms( $product->get_id(), 'product_cat' );
590 if ( $terms && ! is_wp_error( $terms ) ) {
591 foreach ( $terms as $term ) {
592 if ( $term->slug === $slug ) {
593 return true;
594 }
595 }
596 }
597
598 return false;
599 }
600
601 /**
602 * Check if product is out of stock.
603 *
604 * @param $product WC_Product product object.
605 *
606 * @return bool
607 */
608 private function is_out_of_stock( $product ) {
609 return $product !== null && ! $product->is_in_stock();
610 }
611
612 /**
613 * Check if product is featured.
614 *
615 * @param $product WC_Product product object.
616 *
617 * @return bool
618 */
619 private function is_featured( $product ) {
620 return $product !== null && $product->is_featured();
621 }
622
623 /**
624 * Check if the product is new.
625 *
626 * @param $product WC_Product product object.
627 * @param $days number of days to check.
628 *
629 * @return bool
630 */
631 private function is_new( $product, $days ) {
632 $product_creation_date = get_the_date( 'Y-m-d', $product->get_id() );
633
634 $current_date = gmdate( 'Y-m-d' );
635 $days_difference = abs( strtotime( $current_date ) - strtotime( $product_creation_date ) ) / DAY_IN_SECONDS;
636
637 return $days_difference <= $days;
638 }
639 }
640
641 // Initialize the module.
642 add_action( 'init', function () {
643 new Merchant_Product_Labels();
644 } );
645