get_settings(); if (empty($s['enabled'])) { return; } add_action('wp_enqueue_scripts', array($this, 'enqueue_styles')); add_action('woocommerce_before_shop_loop_item', array($this, 'loop_output'), 10); add_action('woocommerce_product_thumbnails', array($this, 'single_output')); add_filter('render_block_woocommerce/product-gallery', array($this, 'inject_block_gallery'), 10, 2); add_filter('render_block_woocommerce/product-image-gallery', array($this, 'inject_block_gallery'), 10, 2); if ( class_exists( 'AIEO_Theme_Adapters' ) && method_exists( 'AIEO_Theme_Adapters', 'draw' ) ) { AIEO_Theme_Adapters::draw( 'product_labels', 'woocommerce_before_shop_loop_item, woocommerce_product_thumbnails, gallery blocks' ); } add_filter('woocommerce_sale_flash', '__return_empty_string', 9999); } public function get_settings($raw = false) { $stored = get_option(self::OPTION_KEY, array()); if (!is_array($stored)) { $stored = array(); } $legacy = get_option('om_ppe_product-labels', array()); if (!is_array($legacy)) { $legacy = array(); } $merged = array_merge(array( 'enabled' => false, 'label_text' => '', 'display_rules' => 'products_on_sale', 'new_products_days' => 7, 'label_position' => 'top-left', 'background_color' => '#212121', 'text_color' => '#ffffff', 'font_size' => 14, 'border_radius' => 5, 'padding_x' => 15, 'padding_y' => 8, 'show_on_single' => true, 'show_on_archive' => true, 'use_shortcode' => false, ), $legacy, $stored); return $raw ? $merged : self::fill_i18n_defaults($merged); } private static function i18n_default() { return __('SALE', 'ai-eshop-optimizer'); } private static function fill_i18n_defaults(array $s) { if (!did_action('init')) { return $s; } if (!isset($s['label_text']) || '' === trim((string) $s['label_text'])) { $s['label_text'] = self::i18n_default(); } return $s; } public function ajax_save() { AIEO_DMM::verify_ajax_request(); $valid_rules = array('products_on_sale','featured_products','new_products','out_of_stock','all_products'); $valid_positions = array('top-left','top-right','bottom-left','bottom-right'); $rule = isset($_POST['display_rules']) ? sanitize_key(wp_unslash($_POST['display_rules'])) : 'products_on_sale'; $pos = isset($_POST['label_position']) ? sanitize_key(wp_unslash($_POST['label_position'])) : 'top-left'; update_option(self::OPTION_KEY, array( 'enabled' => !empty($_POST['enabled']), 'label_text' => isset($_POST['label_text']) ? sanitize_text_field(wp_unslash($_POST['label_text'])) : '', 'display_rules' => in_array($rule, $valid_rules, true) ? $rule : 'products_on_sale', 'new_products_days' => max(1, min(365, isset($_POST['new_products_days']) ? (int) $_POST['new_products_days'] : 7)), 'label_position' => in_array($pos, $valid_positions, true) ? $pos : 'top-left', 'background_color' => isset($_POST['background_color']) ? (sanitize_hex_color(wp_unslash($_POST['background_color'])) ?: '#212121') : '#212121', 'text_color' => isset($_POST['text_color']) ? (sanitize_hex_color(wp_unslash($_POST['text_color'])) ?: '#ffffff') : '#ffffff', 'font_size' => max(10, min(30, isset($_POST['font_size']) ? (int) $_POST['font_size'] : 14)), 'border_radius' => max(0, min(50, isset($_POST['border_radius']) ? (int) $_POST['border_radius'] : 5)), 'padding_x' => max(5, min(50, isset($_POST['padding_x']) ? (int) $_POST['padding_x'] : 15)), 'padding_y' => max(2, min(30, isset($_POST['padding_y']) ? (int) $_POST['padding_y'] : 8)), 'show_on_single' => !empty($_POST['show_on_single']), 'show_on_archive' => !empty($_POST['show_on_archive']), 'use_shortcode' => !empty($_POST['use_shortcode']), ), false); wp_send_json_success(array('message' => __('Product Labels settings saved.', 'ai-eshop-optimizer'))); } public function enqueue_styles() { if (class_exists('AIEO_DMM_Badge_Renderer') && AIEO_DMM_Badge_Renderer::is_enabled()) { return; } wp_register_style('aieo-product-labels', false); wp_enqueue_style('aieo-product-labels'); $css = '.woocommerce .onsale,.wc-block-grid__product-onsale,.wc-block-grid__product .onsale{display:none !important;}'; wp_add_inline_style('aieo-product-labels', $css); } public function loop_output() { if (class_exists('AIEO_DMM_Badge_Renderer') && AIEO_DMM_Badge_Renderer::is_enabled()) { return; } $s = $this->get_settings(); if (empty($s['show_on_archive'])) { return; } global $product; if (!$product) { return; } echo wp_kses_post($this->get_label_html($product)); } public function single_output() { if (class_exists('AIEO_DMM_Badge_Renderer') && AIEO_DMM_Badge_Renderer::is_enabled()) { return; } $s = $this->get_settings(); if (!empty($s['use_shortcode']) || empty($s['show_on_single'])) { return; } global $product; if (!$product) { return; } echo wp_kses_post($this->get_label_html($product)); } public function inject_block_gallery($block_content, $block) { if (class_exists('AIEO_DMM_Badge_Renderer') && AIEO_DMM_Badge_Renderer::is_enabled()) { return $block_content; } if (!is_singular('product')) { return $block_content; } $s = $this->get_settings(); if (!empty($s['use_shortcode']) || empty($s['show_on_single'])) { return $block_content; } $product = isset($GLOBALS['product']) && $GLOBALS['product'] instanceof WC_Product ? $GLOBALS['product'] : wc_get_product(get_queried_object_id()); if (!$product) { return $block_content; } $label = wp_kses_post($this->get_label_html($product)); if ($label === '') { return $block_content; } return preg_replace('/(]*wp-block-woocommerce-product-(?:image-)?gallery[^>]*>)/', '$1' . $label, (string) $block_content, 1); } public function shortcode_handler($atts = array()) { $s = $this->get_settings(); if (empty($s['enabled']) || empty($s['use_shortcode'])) { return ''; } if (!is_singular('product')) { if (current_user_can('manage_options')) { return '

' . esc_html__('Product Labels shortcode can only be used on single product pages.', 'ai-eshop-optimizer') . '

'; } return ''; } global $product; return $product ? $this->get_label_html($product) : ''; } private function get_label_html($product) { if (!$this->should_display_label($product)) { return ''; } $s = $this->get_settings(); $label = (string) $s['label_text']; if ((strpos($label, '{sale}') !== false || strpos($label, '{sale_amount}') !== false) && class_exists('AIEO_Catalogue_Mode') && AIEO_Catalogue_Mode::prices_off($product)) { return ''; } if (strpos($label, '{sale}') !== false) { $label = str_replace('{sale}', $this->sale_pct($product) . '%', $label); } if (strpos($label, '{sale_amount}') !== false) { $label = str_replace('{sale_amount}', wc_price($this->sale_amount($product)), $label); } $position_class = 'position-' . esc_attr((string) $s['label_position']); $style = sprintf( 'background-color:%s;color:%s;font-size:%dpx;border-radius:%dpx;padding:%dpx %dpx;', esc_attr((string) $s['background_color']), esc_attr((string) $s['text_color']), (int) $s['font_size'], (int) $s['border_radius'], (int) $s['padding_y'], (int) $s['padding_x'] ); return sprintf( '
%s
', $position_class, esc_attr($style), esc_html($label) ); } private function should_display_label($product) { if (!$product) { return false; } $s = $this->get_settings(); $rule = (string) $s['display_rules']; switch ($rule) { case 'products_on_sale': $display = $product->is_on_sale(); break; case 'featured_products': $display = $product->is_featured(); break; case 'out_of_stock': $display = !$product->is_in_stock(); break; case 'all_products': $display = true; break; case 'new_products': $days = (int) $s['new_products_days']; $display = $this->is_new_product($product, $days); break; default: $display = $product->is_on_sale(); break; } return (bool) apply_filters('om_ppe_product_label_display', $display, $product, $rule); } public static function add_eligibility_row($rows, $product_id, $audience_key) { if (!is_array($rows)) { $rows = array(); } $product_id = (int) $product_id; if ($product_id === 0) { return $rows; } if (!class_exists('AIEO_Effective_Eligibility')) { return $rows; } $r = self::is_eligible_for($product_id, (string) $audience_key); if (!empty($r['eligible'])) { $rows[AIEO_Effective_Eligibility::SURFACE_PRODUCT_LABEL] = $r; } return $rows; } public static function is_eligible_for($product_id, $audience_key = 'guest') { $product_id = (int) $product_id; if ($product_id <= 0) { return array('eligible' => false); } if (!function_exists('wc_get_product')) { return array('eligible' => false); } $product = wc_get_product($product_id); if (!$product) { return array('eligible' => false); } $instance = self::eligibility_instance(); $s = $instance->get_settings(); if (empty($s['enabled'])) { return array('eligible' => false); } $rule = isset($s['display_rules']) ? (string) $s['display_rules'] : 'products_on_sale'; switch ($rule) { case 'products_on_sale': $display = $product->is_on_sale(); break; case 'featured_products': $display = $product->is_featured(); break; case 'out_of_stock': $display = !$product->is_in_stock(); break; case 'all_products': $display = true; break; case 'new_products': $days = isset($s['new_products_days']) ? (int) $s['new_products_days'] : 7; $display = $instance->is_new_product($product, $days); break; default: $display = $product->is_on_sale(); break; } $display = (bool) apply_filters('om_ppe_product_label_display', $display, $product, $rule); if (!$display) { return array('eligible' => false); } return array( 'eligible' => true, 'offer_id' => null, 'payload' => array( 'rule' => $rule, 'label' => isset($s['label_text']) ? (string) $s['label_text'] : self::i18n_default(), ), ); } private static function eligibility_instance() { static $inst = null; if ($inst === null) { if (class_exists('AIEO_DMM') && method_exists('AIEO_DMM', 'instance')) { $mod = AIEO_DMM::instance()->get_module('product_labels'); $inst = ($mod instanceof self) ? $mod : new self(); } else { $inst = new self(); } } return $inst; } private function is_new_product($product, $days) { $product_date = (string) get_the_date('Y-m-d', $product->get_id()); $current_date = gmdate('Y-m-d'); $diff = abs(strtotime($current_date) - strtotime($product_date)) / DAY_IN_SECONDS; return $diff <= $days; } private function sale_pct($product) { if ($product->is_type('variable')) { $reg = (float) $product->get_variation_regular_price('min', true); $sal = (float) $product->get_variation_sale_price('min', true); } else { $reg = (float) $product->get_regular_price(); $sal = (float) $product->get_sale_price(); } if ($reg <= 0 || $sal <= 0) { return 0; } return (int) round(100 - ($sal / $reg * 100)); } private function sale_amount($product) { if ($product->is_type('variable')) { $reg = (float) $product->get_variation_regular_price('min', true); $sal = (float) $product->get_variation_sale_price('min', true); } else { $reg = (float) $product->get_regular_price(); $sal = (float) $product->get_sale_price(); } return max(0, $reg - $sal); } public function render_subtab() { $s = $this->get_settings(); $id_of = static function ( $name ) { return 'aieo-pll-' . str_replace( '_', '-', $name ); }; $row = static function ( $name, $label, $control, $help = '' ) use ( $id_of ) { $field = array( 'id' => $id_of( $name ), 'label' => $label, 'control' => $control ); if ( '' !== $help ) { $field['help'] = $help; } return \Rooster\Admin\Ui::field_row( $field ); }; $switch = static function ( $name, $text, $hidden_label = false ) use ( $s, $id_of ) { return \Rooster\Admin\Ui::toggle( $name, ! empty( $s[ $name ] ), array( 'id' => $id_of( $name ), 'hidden' => false, 'label' => $text, 'label_hidden' => $hidden_label ) ); }; $px = static function ( $name, $min, $max ) use ( $s, $id_of ) { return \Rooster\Admin\Ui::input( $name, (string) (int) $s[ $name ], array( 'id' => $id_of( $name ), 'type' => 'number', 'xs' => true, 'unit' => 'px', 'min' => $min, 'max' => $max ) ); }; $card = static function ( $anchor, $icon, $title, $col, $fields, $description = '' ) { $form_id = 'aieo-card-' . $anchor . '-form'; list( $open, $close ) = AIEO_DMM_UI::card( array( 'anchor' => $anchor, 'icon' => $icon, 'title' => $title, 'description' => $description, 'col' => $col, 'footer' => class_exists( '\Rooster\Admin\States' ) ? \Rooster\Admin\States::save_button( $form_id ) : '', ) ); echo $open; echo AIEO_DMM_UI::card_form( 'aieo_dmm_product_labels_save', 'product_labels', $form_id, '
' . $fields . '
' ); echo $close; }; rooster_admin_band_open( array( 'cols' => 2 ) ); $rules = array( 'products_on_sale' => __( 'Products on sale', 'ai-eshop-optimizer' ), 'featured_products' => __( 'Featured products', 'ai-eshop-optimizer' ), 'new_products' => __( 'New products', 'ai-eshop-optimizer' ), 'out_of_stock' => __( 'Out of stock', 'ai-eshop-optimizer' ), 'all_products' => __( 'All products', 'ai-eshop-optimizer' ), ); $positions = array( 'top-left' => __( 'Top left', 'ai-eshop-optimizer' ), 'top-right' => __( 'Top right', 'ai-eshop-optimizer' ), 'bottom-left' => __( 'Bottom left', 'ai-eshop-optimizer' ), 'bottom-right' => __( 'Bottom right', 'ai-eshop-optimizer' ) ); $fields = $row( 'enabled', __( 'Enable', 'ai-eshop-optimizer' ), $switch( 'enabled', __( 'On', 'ai-eshop-optimizer' ), true ) ); $fields .= $row( 'label_text', __( 'Label text', 'ai-eshop-optimizer' ), \Rooster\Admin\Ui::input( 'label_text', (string) $s['label_text'], array( 'id' => $id_of( 'label_text' ), 'medium' => true, 'placeholder' => self::i18n_default() ) ), __( 'Tokens: {sale} = discount percentage, {sale_amount} = discount amount.', 'ai-eshop-optimizer' ) ); $fields .= $row( 'display_rules', __( 'Display on', 'ai-eshop-optimizer' ), \Rooster\Admin\Ui::select( 'display_rules', $rules, (string) $s['display_rules'], array( 'id' => $id_of( 'display_rules' ) ) ) ); $fields .= $row( 'new_products_days', __( '"New" days threshold', 'ai-eshop-optimizer' ), \Rooster\Admin\Ui::input( 'new_products_days', (string) (int) $s['new_products_days'], array( 'id' => $id_of( 'new_products_days' ), 'type' => 'number', 'xs' => true, 'unit' => __( 'days', 'ai-eshop-optimizer' ), 'min' => 1, 'max' => 365 ) ), __( 'Only used when display rule is "New products".', 'ai-eshop-optimizer' ) ); $fields .= $row( 'label_position', __( 'Position', 'ai-eshop-optimizer' ), \Rooster\Admin\Ui::select( 'label_position', $positions, (string) $s['label_position'], array( 'id' => $id_of( 'label_position' ) ) ) ); $card( 'product-labels-overview', 'tag', __( 'Settings', 'ai-eshop-optimizer' ), 1, $fields, __( 'Turn the product label on, write its text and choose which products carry it and in which corner it sits.', 'ai-eshop-optimizer' ) ); $fields = $row( 'background_color', __( 'Background', 'ai-eshop-optimizer' ), \Rooster\Admin\Fields::color( 'background_color', (string) $s['background_color'], array( 'id' => $id_of( 'background_color' ), 'default' => '#212121' ) ) ); $fields .= $row( 'text_color', __( 'Text colour', 'ai-eshop-optimizer' ), \Rooster\Admin\Fields::color( 'text_color', (string) $s['text_color'], array( 'id' => $id_of( 'text_color' ), 'default' => '#ffffff' ) ) ); $fields .= $row( 'font_size', __( 'Font size', 'ai-eshop-optimizer' ), $px( 'font_size', 10, 30 ) ); $fields .= $row( 'border_radius', __( 'Border radius', 'ai-eshop-optimizer' ), $px( 'border_radius', 0, 50 ) ); $fields .= \Rooster\Admin\Ui::field_row( array( 'id' => $id_of( 'padding' ), 'label' => __( 'Padding (X / Y)', 'ai-eshop-optimizer' ), 'joiner' => '×', 'parts' => array( array( 'label' => __( 'X', 'ai-eshop-optimizer' ), 'id' => $id_of( 'padding_x' ), 'control' => $px( 'padding_x', 5, 50 ) ), array( 'label' => __( 'Y', 'ai-eshop-optimizer' ), 'id' => $id_of( 'padding_y' ), 'control' => $px( 'padding_y', 2, 30 ) ), ), ) ); $card( 'product-labels-style', 'palette', __( 'Style', 'ai-eshop-optimizer' ), 2, $fields, __( 'Set the label\'s background and text colours, its text size, rounded corners and inner spacing.', 'ai-eshop-optimizer' ) ); $fields = $row( 'show_on_single', __( 'Single product page', 'ai-eshop-optimizer' ), $switch( 'show_on_single', __( 'On', 'ai-eshop-optimizer' ), true ) ); $fields .= $row( 'show_on_archive', __( 'Shop / archive pages', 'ai-eshop-optimizer' ), $switch( 'show_on_archive', __( 'On', 'ai-eshop-optimizer' ), true ) ); $fields .= $row( 'use_shortcode', __( 'Use shortcode for single (manual)', 'ai-eshop-optimizer' ), $switch( 'use_shortcode', '[om_ppe_product_labels]' ) ); $card( 'product-labels-where-to-show', 'map-pin', __( 'Where to show', 'ai-eshop-optimizer' ), 1, $fields, __( 'Choose whether the label shows on product pages and on shop and category pages, or place it on product pages with a shortcode.', 'ai-eshop-optimizer' ) ); rooster_admin_band_close(); } }