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();
}
}