is_runtime_enabled() ) { return; } if ( ! self::cache_operational() ) { return; } add_action( 'wp_head', array( $this, 'output_custom_css' ), 100 ); add_action( 'wp_head', array( $this, 'output_collapse_secondary_css' ), 101 ); add_action( 'loop_start', array( $this, 'prime_card_loop' ) ); add_action( 'wp_head', array( $this, 'output_early_multi_select_css' ), 1 ); add_action( 'woocommerce_after_variations_table', array( $this, 'print_multi_select_boxes' ) ); add_filter( 'woocommerce_available_variation', array( $this, 'note_variation_stock' ), 10, 3 ); add_filter( 'rooster/used_css/safelist', array( __CLASS__, 'shades_safelist' ) ); add_action( 'wp', array( $this, 'setup_archive_swatches' ) ); add_filter( 'render_block_core/post-title', array( $this, 'title_line_in_title' ), 10, 3 ); add_action( 'woocommerce_single_product_summary', array( $this, 'print_title_line' ), 6 ); add_action( 'wp', array( $this, 'maybe_remove_default_add_to_cart' ), 20 ); add_action( 'wp_footer', array( $this, 'output_cart_variations_data_tag' ) ); add_filter( 'woocommerce_add_to_cart_fragments', array( $this, 'add_cart_variations_fragment' ) ); add_filter( 'body_class', array( $this, 'filter_body_class' ) ); add_filter( 'woocommerce_ajax_variation_threshold', array( $this, 'filter_ajax_variation_threshold' ), 10, 2 ); add_action( 'wc_ajax_aieo_card_form', array( $this, 'ajax_card_form' ) ); add_filter( 'rooster/performance/delay_keep', array( __CLASS__, 'keep_card_script' ) ); add_filter( 'woocommerce_dropdown_variation_attribute_options_html', array( $this, 'filter_variation_attribute_options_html' ), 10, 2 ); if ( class_exists( 'AIEO_Theme_Adapters' ) ) { add_action( 'wp', array( $this, 'arbitrate_with_theme' ), 5 ); } add_filter( 'woocommerce_cart_item_thumbnail', array( $this, 'filter_cart_item_thumbnail' ), 10, 3 ); add_filter( 'style_loader_src', array( $this, 'veto_swatch_skin' ), 10, 2 ); add_action( 'shutdown', array( $this, 'flush_pending_rebuilds' ) ); } private $swatch_mode = null; private $stand_down = null; const SETTINGS_OPTION = 'aieo_dmm_swatches_settings'; const OLD_SWITCH = 'aieo_dmm_swatches_runtime_enabled'; const ONE_SWITCH = 'aieo_dmm_swatches_one_switch'; public static function on() { if ( ! get_option( self::ONE_SWITCH ) ) { self::carry_over(); } $s = get_option( self::SETTINGS_OPTION, array() ); return is_array( $s ) && 'yes' === (string) ( $s['enabled'] ?? 'no' ); } public static function carry_over() { $old = get_option( self::OLD_SWITCH, null ); $s = get_option( self::SETTINGS_OPTION, array() ); $s = is_array( $s ) ? $s : array(); $want = 'yes' === (string) $old ? 'yes' : 'no'; if ( ( $s['enabled'] ?? null ) !== $want ) { $s['enabled'] = $want; $hash = get_option( 'aieo_dmm_swatches_settings_hash', null ); update_option( self::SETTINGS_OPTION, $s, false ); if ( null !== $hash ) { update_option( 'aieo_dmm_swatches_settings_hash', $hash, false ); } } delete_option( self::OLD_SWITCH ); update_option( self::ONE_SWITCH, 1, true ); } private function is_runtime_enabled() { return self::on(); } public static function cache_operational() { $state = get_option( self::CACHE_READY_OPTION, '' ); if ( 'yes' !== $state && 'no' !== $state ) { global $wpdb; $table = $wpdb->prefix . 'aieo_dmm_swatches_cache'; $exists = function_exists( 'aieo_dmm_table_exists' ) ? aieo_dmm_table_exists( $table ) : ( $table === $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) ) ); $state = ( $exists && $wpdb->get_var( "SELECT 1 FROM {$table} LIMIT 1" ) ) ? 'yes' : 'no'; update_option( self::CACHE_READY_OPTION, $state, true ); } return 'yes' === $state; } private function get_settings() { if ( $this->settings === null ) { $stored = get_option( 'aieo_dmm_swatches_settings', array() ); $this->settings = is_array( $stored ) ? $stored : array(); $this->settings_hash = (string) get_option( 'aieo_dmm_swatches_settings_hash', '' ); if ( $this->settings_hash !== '' && class_exists( 'AIEO_DMM_Swatches' ) ) { $this->settings_hash .= AIEO_DMM_Swatches::SWATCH_PAYLOAD_SUFFIX; } } return $this->settings; } public function output_collapse_secondary_css() { if ( ! wp_style_is( 'aieo-swatches', 'enqueued' ) ) { return; } echo ''; } public function output_custom_css() { if ( class_exists( 'OM_Product_Swatches_Handler' ) ) { return; } $s = $this->get_settings(); $measures = self::theme_swatch_measures(); $v = function ( $key, $default ) use ( $s, $measures ) { $val = ( ! isset( $s[ $key ] ) || $s[ $key ] === '' ) ? $default : $s[ $key ]; return self::themed_value( (string) $key, $val, $default, $measures ); }; $css = ''; $css .= ".om-variation-type-color :is(a,label) {" . "width:{$v('color_width',24)}px;height:{$v('color_height',24)}px;" . "border-radius:{$v('color_border_radius',50)}px;" . "border:{$v('color_border_size',1)}px solid {$v('color_border_color','#dedede')};" . "}"; $css .= ".om-variation-type-color :is(a,label):hover{" . "border:{$v('color_border_size_hover',2)}px solid {$v('color_border_color_hover','#212121')};" . "}"; $css .= ".om-variation-type-color :is(a,label).active,.om-variation-type-color a[aria-checked='true'],.om-variation-type-color label:has(> .om-variation-input:checked){" . "border:{$v('color_border_size_selected',2)}px solid {$v('color_border_color_selected','#212121')};" . "}"; $css .= ".om-variation-type-color{gap:{$v('color_gap',8)}px;}"; $css .= ".om-variation-type-button :is(a,label){" . "color:{$v('button_text_color','#212121')};" . "background-color:{$v('button_bg_color','#ffffff')};" . "border:{$v('button_border_size',1)}px solid {$v('button_border_color','#212121')};" . "border-radius:{$v('button_border_radius',5)}px;" . "padding:{$v('button_padding_top',8)}px {$v('button_padding_right',12)}px {$v('button_padding_bottom',8)}px {$v('button_padding_left',12)}px;" . "}"; $css .= ".om-variation-type-button :is(a,label):hover{" . "color:{$v('button_text_color_hover','#ffffff')};" . "background-color:{$v('button_bg_color_hover','#212121')};" . "border-color:{$v('button_border_color_hover','#212121')};" . "}"; $css .= ".om-variation-type-button :is(a,label).active,.om-variation-type-button a[aria-checked='true'],.om-variation-type-button label:has(> .om-variation-input:checked){" . "color:{$v('button_text_color_selected','#ffffff')};" . "background-color:{$v('button_bg_color_selected','#212121')};" . "border-color:{$v('button_border_color_selected','#212121')};" . "}"; $css .= ".om-variation-type-button{gap:{$v('button_gap',8)}px;}"; $css .= ".om-variation-type-image :is(a,label){" . "width:{$v('image_width',72)}px;height:{$v('image_height',72)}px;" . "border-radius:{$v('image_border_radius',0)}px;" . "border:{$v('image_border_size',1)}px solid {$v('image_border_color','#dedede')};" . "overflow:hidden;}"; $css .= ".om-variation-type-image :is(a,label) img{border-radius:{$v('image_border_radius',0)}px;}"; $css .= ".om-variation-type-image :is(a,label):hover{" . "border:{$v('image_border_size_hover',2)}px solid {$v('image_border_color_hover','#212121')};" . "}"; $css .= ".om-variation-type-image :is(a,label).active,.om-variation-type-image a[aria-checked='true'],.om-variation-type-image label:has(> .om-variation-input:checked){" . "border:{$v('image_border_size_selected',2)}px solid {$v('image_border_color_selected','#212121')};" . "}"; $css .= ".om-variation-type-image{gap:{$v('image_gap',12)}px;}"; $css .= $this->build_archive_css( $v ); $css .= $this->build_recommendation_css( $v ); $css .= 'body.om-multi-select-enabled .variations_form table.variations,' . 'body.om-multi-select-enabled .variations_form table.variations tbody,' . 'body.om-multi-select-enabled .variations_form table.variations tr' . '{display:block;width:100%;}' . 'body.om-multi-select-enabled .variations_form table.variations th.label' . '{display:none;}' . 'body.om-multi-select-enabled .variations_form table.variations td.value' . '{display:block;padding:0;width:100%;}'; $mobile_css = ''; $mw_pdp = (int) $v( 'image_width_mobile', 56 ); $mh_pdp = (int) $v( 'image_height_mobile', 56 ); $mw_arc = (int) $v( 'image_width_shop_archive_mobile', 22 ); $mh_arc = (int) $v( 'image_height_shop_archive_mobile', 22 ); if ( $mw_pdp > 0 || $mh_pdp > 0 ) { $mobile_css .= '.om-variation-type-image :is(a,label){'; if ( $mw_pdp > 0 ) { $mobile_css .= "width:{$mw_pdp}px;"; } if ( $mh_pdp > 0 ) { $mobile_css .= "height:{$mh_pdp}px;"; } $mobile_css .= '}'; } if ( $mw_arc > 0 || $mh_arc > 0 ) { $arc_selectors = '.om-product-swatches--archive .om-variation-type-image a,' . '.related.products .om-variation-type-image a,' . '.cross-sells .om-variation-type-image a,' . '.up-sells .om-variation-type-image a,' . 'section.related .om-variation-type-image a'; $mobile_css .= $arc_selectors . '{'; if ( $mw_arc > 0 ) { $mobile_css .= "width:{$mw_arc}px;"; } if ( $mh_arc > 0 ) { $mobile_css .= "height:{$mh_arc}px;"; } $mobile_css .= '}'; } $bpt = (int) $v( 'button_padding_top_mobile', 4 ); $bpr = (int) $v( 'button_padding_right_mobile', 8 ); $bpb = (int) $v( 'button_padding_bottom_mobile', 4 ); $bpl = (int) $v( 'button_padding_left_mobile', 8 ); $bfs = (int) $v( 'button_font_size_mobile', 11 ); if ( $bpt > 0 || $bpr > 0 || $bpb > 0 || $bpl > 0 || $bfs > 0 ) { $mobile_css .= '.om-variation-type-button :is(a,label){'; if ( $bpt > 0 || $bpr > 0 || $bpb > 0 || $bpl > 0 ) { $mobile_css .= "padding:{$bpt}px {$bpr}px {$bpb}px {$bpl}px;"; } if ( $bfs > 0 ) { $mobile_css .= "font-size:{$bfs}px;"; } $mobile_css .= '}'; } if ( $mobile_css !== '' ) { $css .= '@media (max-width:600px){' . $mobile_css . '}'; } if ( ! empty( $v( 'button_stack_vertical_archive', 0 ) ) ) { $row_sel = '.wc-block-product-template .om-product-swatches--archive .variations tr:has(.om-variation-type-button),' . '.wp-block-woocommerce-product-template .om-product-swatches--archive .variations tr:has(.om-variation-type-button)'; $list_sel = '.wc-block-product-template .om-product-swatches--archive .om-variation-type-button,' . '.wp-block-woocommerce-product-template .om-product-swatches--archive .om-variation-type-button'; $css .= $row_sel . '{' . 'position:absolute !important;' . 'top:25% !important;' . 'transform:translateY(-50%) !important;' . 'left:var(--wp--preset--spacing--3,12px) !important;' . 'right:auto !important;' . 'z-index:5 !important;' . 'width:auto !important;' . 'margin:0 !important;' . '}'; $css .= $list_sel . '{' . 'display:flex !important;' . 'flex-direction:column !important;' . 'align-items:center !important;' . 'justify-content:flex-start !important;' . '}'; if ( ! empty( $v( 'button_circle_archive', 0 ) ) ) { $diam = max( 20, (int) $v( 'button_circle_diameter_archive', 36 ) ); $fs = max( 6, (int) $v( 'button_font_size_shop_archive', 11 ) ); $a_sel = '.wc-block-product-template .om-product-swatches--archive .om-variation-type-button a,' . '.wp-block-woocommerce-product-template .om-product-swatches--archive .om-variation-type-button a'; $css .= $a_sel . '{' . 'width:' . $diam . 'px !important;' . 'height:' . $diam . 'px !important;' . 'border-radius:50% !important;' . 'padding:0 !important;' . 'font-size:' . $fs . 'px !important;' . 'display:flex !important;' . 'align-items:center !important;' . 'justify-content:center !important;' . 'line-height:1 !important;' . 'overflow:hidden !important;' . 'white-space:nowrap !important;' . 'text-align:center !important;' . '}'; } } $css = str_replace( ')px', ')', $css ); if ( 'designer' === $this->look() ) { $css .= self::designer_rows_css(); } if ( $css !== '' && wp_style_is( 'aieo-swatches', 'enqueued' ) ) { echo ''; } } public static function theme_swatch_measures() { static $measures = null; if ( null === $measures ) { $found = function_exists( 'wp_get_global_settings' ) ? wp_get_global_settings( array( 'custom', 'rooster', 'swatch' ) ) : array(); $measures = is_array( $found ) ? array_filter( $found, 'is_scalar' ) : array(); } return (array) apply_filters( 'aieo_swatches_theme_measures', $measures ); } public static function themed_value( $key, $value, $default, array $measures ) { if ( 0 !== strcasecmp( trim( (string) $value ), trim( (string) $default ) ) ) { return $value; } if ( false !== strpos( $key, '_color' ) ) { $roles = array( '#dedede' => 'line', '#212121' => 'fg', '#ffffff' => 'bg' ); $hex = strtolower( trim( (string) $value ) ); return isset( $roles[ $hex ] ) ? 'var(--wp--preset--color--' . $roles[ $hex ] . ',' . $hex . ')' : $value; } $card = false !== strpos( $key, '_shop_archive' ); $name = ''; if ( 1 === preg_match( '/^image_(?:width|height)(?:_shop_archive)?_mobile$/', $key ) ) { return isset( $measures[ $card ? 'size-card' : 'size' ] ) ? '' : $value; } elseif ( 1 === preg_match( '/^(?:color|image)_(?:width|height)(?:_shop_archive)?$/', $key ) ) { $name = $card ? 'size-card' : 'size'; } elseif ( 1 === preg_match( '/^(?:color|image|button)_gap(?:_shop_archive)?$/', $key ) ) { $name = $card ? 'gap-card' : 'gap'; } elseif ( 1 === preg_match( '/^(?:color|image)_border_radius(?:_shop_archive)?$/', $key ) ) { $name = 'shape'; } return ( '' !== $name && isset( $measures[ $name ] ) ) ? 'var(--wp--custom--rooster--swatch--' . $name . ',' . (int) $value . 'px)' : $value; } public static function assets_in_context( $in_context ) { if ( $in_context || self::$assets_wanted ) { return true; } $post = function_exists( 'get_post' ) ? get_post() : null; return $post instanceof WP_Post && ( has_block( 'aieo/product-card-swatches', $post ) || has_shortcode( (string) $post->post_content, 'aieo_product_swatches' ) ); } public static function want_assets() { self::$assets_wanted = true; } private function look() { $s = $this->get_settings(); return 'classic' === (string) ( $s['product_swatch_look'] ?? 'designer' ) ? 'classic' : 'designer'; } private function designer_draws( $is_archive, $control, $attribute_type, $product_id ) { return ! $is_archive && '' !== $control && in_array( $attribute_type, array( 'color', 'image' ), true ) && 'designer' === $this->look() && is_singular( 'product' ) && (int) get_queried_object_id() === (int) $product_id; } private $low_stock = array(); public function note_variation_stock( $data, $product = null, $variation = null ) { if ( $variation instanceof WC_Product && $variation->managing_stock() && $variation->is_in_stock() ) { $left = (int) $variation->get_stock_quantity(); $low = function_exists( 'wc_get_low_stock_amount' ) ? (int) wc_get_low_stock_amount( $variation ) : (int) get_option( 'woocommerce_notify_low_stock_amount', 2 ); if ( $left > 0 && $left <= $low ) { $this->low_stock[ (int) $variation->get_id() ] = $left; } } return $data; } private function cart_variation_ids( $product_id ) { if ( empty( $_COOKIE['woocommerce_items_in_cart'] ) || ! function_exists( 'WC' ) || ! WC()->cart ) { return array(); } $ids = array(); foreach ( WC()->cart->get_cart() as $item ) { if ( (int) ( $item['product_id'] ?? 0 ) === (int) $product_id && ! empty( $item['variation_id'] ) ) { $ids[ (int) $item['variation_id'] ] = ( $ids[ (int) $item['variation_id'] ] ?? 0 ) + max( 1, (int) ( $item['quantity'] ?? 1 ) ); } } return $ids; } private function chip_in_words( $html, $left, $bag ) { if ( ( $left <= 0 && $bag <= 0 ) || ! class_exists( 'WP_HTML_Tag_Processor' ) ) { return $html; } $tags = new WP_HTML_Tag_Processor( $html ); if ( ! $tags->next_tag( 'label' ) ) { return $html; } if ( $left > 0 ) { $tags->set_attribute( 'data-left', (string) $left ); } if ( $bag > 0 ) { $tags->set_attribute( 'data-in-bag', (string) $bag ); } if ( $tags->next_tag( 'input' ) ) { $name = (string) $tags->get_attribute( 'aria-label' ); $words = array(); if ( $left > 0 ) { $words[] = sprintf( __( 'Only %d left', 'ai-eshop-optimizer' ), $left ); } if ( $bag > 0 ) { $words[] = sprintf( __( 'In your bag: %s', 'ai-eshop-optimizer' ), number_format_i18n( $bag ) ); } $tags->set_attribute( 'aria-label', implode( ', ', array_merge( '' !== $name ? array( $name ) : array(), $words ) ) ); } return $tags->get_updated_html(); } public static function designer_rows_css() { $m = self::theme_swatch_measures(); $px = static function ( $value, $fallback ) { return is_scalar( $value ) && 1 === preg_match( '/^\s*(\d+(?:\.\d+)?)\s*(?:px)?\s*$/', (string) $value, $hit ) ? (float) $hit[1] : (float) $fallback; }; $size = max( 8.0, $px( $m['size'] ?? '', 40 ) ); $gap = max( 0.0, $px( $m['gap'] ?? '', 10 ) ); $rows = max( 1, (int) ( $m['rows'] ?? 2 ) ); $root = '.om-variations-wrapper[data-look="designer"]'; $css = ''; for ( $n = 1; $n <= 24; $n++ ) { $from = $n * $size + ( $n - 1 ) * $gap; $to = ( $n + 1 ) * $size + $n * $gap - 0.02; $when = array(); if ( $n > 1 ) { $when[] = '(min-width:' . $from . 'px)'; } if ( $n < 24 ) { $when[] = '(max-width:' . $to . 'px)'; } $past = $rows * $n + 1; $css .= '@container aieo-shades ' . implode( ' and ', $when ) . '{' . $root . ' .aieo-shades__chips>.om-variation-item:nth-child(n+' . $past . '){display:none}' . $root . ':not(:has(.aieo-shades__chips>.om-variation-item:nth-child(' . $past . '))) .aieo-shades__more{display:none}' . '}'; } return $css; } public static function shades_safelist( $list ) { $list = (array) $list; $list[] = '/^aieo-shades/'; $list[] = '/^om-multi-selected$/'; return $list; } public static function shades_i18n() { $quote = self::quote_words(); return array( 'chooseSome' => __( 'choose one or more', 'ai-eshop-optimizer' ), 'chooseShade' => __( 'Choose a shade', 'ai-eshop-optimizer' ), 'chosenOne' => __( '%s chosen', 'ai-eshop-optimizer' ), 'chosenMany' => _x( '%s chosen', 'several shades', 'ai-eshop-optimizer' ), 'bagOne' => __( '%s already in your bag', 'ai-eshop-optimizer' ), 'bagMany' => _x( '%s already in your bag', 'several shades', 'ai-eshop-optimizer' ), 'stockOne' => _n( '%s shade in stock', '%s shades in stock', 1, 'ai-eshop-optimizer' ), 'stockMany' => _n( '%s shade in stock', '%s shades in stock', 2, 'ai-eshop-optimizer' ), 'named' => __( '%1$s: %2$s', 'ai-eshop-optimizer' ), 'inStock' => __( 'In stock', 'ai-eshop-optimizer' ), 'outOfStock' => __( 'Out of stock', 'ai-eshop-optimizer' ), 'inYourBag' => __( 'In your bag: %s', 'ai-eshop-optimizer' ), 'askNone' => $quote['ask'], 'askAddNone' => $quote['add'], 'askOne' => __( 'Request a quote for %s shade', 'ai-eshop-optimizer' ), 'askMany' => __( 'Request a quote for %s shades', 'ai-eshop-optimizer' ), 'askAddOne' => __( 'Add %s shade to my quote', 'ai-eshop-optimizer' ), 'askAddMany' => __( 'Add %s shades to my quote', 'ai-eshop-optimizer' ), 'shadeOne' => _n( '%s shade', '%s shades', 1, 'ai-eshop-optimizer' ), 'shadeMany' => _n( '%s shade', '%s shades', 2, 'ai-eshop-optimizer' ), 'allOne' => _n( 'All %s shade', 'All %s shades', 1, 'ai-eshop-optimizer' ), 'allMany' => _n( 'All %s shade', 'All %s shades', 2, 'ai-eshop-optimizer' ), 'addN' => __( 'Add %1$s to cart · %2$s', 'ai-eshop-optimizer' ), 'find' => __( 'Find a shade', 'ai-eshop-optimizer' ), 'noMatch' => __( 'No shade matches.', 'ai-eshop-optimizer' ), 'less' => __( 'One less %s', 'ai-eshop-optimizer' ), 'more' => __( 'One more %s', 'ai-eshop-optimizer' ), 'quantityOf' => __( 'Quantity of %s', 'ai-eshop-optimizer' ), 'soldOut' => __( 'Sold out', 'ai-eshop-optimizer' ), 'backorder' => __( 'Available on back-order', 'ai-eshop-optimizer' ), 'left' => __( 'Only %d left', 'ai-eshop-optimizer' ), 'chosenSay' => __( 'Chosen: %s.', 'ai-eshop-optimizer' ), 'removedSay' => __( 'Removed: %s.', 'ai-eshop-optimizer' ), 'nothing' => __( 'Nothing chosen.', 'ai-eshop-optimizer' ), 'cleared' => __( 'Selections cleared.', 'ai-eshop-optimizer' ), 'done' => __( 'Done', 'ai-eshop-optimizer' ), 'close' => __( 'Close', 'ai-eshop-optimizer' ), ); } private static function quote_words() { $ask = class_exists( 'AIEO_Catalogue_Mode' ) ? (string) AIEO_Catalogue_Mode::request_label() : ''; $add = class_exists( 'AIEO_Product_Request_Settings' ) ? trim( (string) ( AIEO_Product_Request_Settings::get()['add_button_text'] ?? '' ) ) : ''; return array( 'ask' => '' !== $ask ? $ask : __( 'Request a quote', 'ai-eshop-optimizer' ), 'add' => '' !== $add ? $add : __( 'Add to my quote as well', 'ai-eshop-optimizer' ), ); } private $quote_for = 0; private $quote_one = array(); public function quote_picker_draws( $product_id ) { $product_id = (int) $product_id; if ( $product_id <= 0 || ! function_exists( 'woocommerce_variable_add_to_cart' ) || ! $this->is_runtime_enabled() || ! self::cache_operational() ) { return false; } if ( ! is_singular( 'product' ) || (int) get_queried_object_id() !== $product_id ) { return false; } if ( false === has_filter( 'woocommerce_dropdown_variation_attribute_options_html', array( $this, 'filter_variation_attribute_options_html' ) ) ) { return false; } $ps = $this->resolve_product_settings( $product_id ); if ( ! empty( $ps['multi_select_enabled'] ) && 'dropdown' === (string) ( $ps['display_style'] ?? 'grid' ) ) { return false; } return $this->swatches_draw( $product_id ); } public function quote_form_html( $product_id ) { $product = function_exists( 'wc_get_product' ) ? wc_get_product( (int) $product_id ) : null; if ( ! $product instanceof WC_Product || ! $product->is_type( 'variable' ) || ! $this->quote_picker_draws( (int) $product_id ) ) { return ''; } global $wp_filter; $pid = (int) $product->get_id(); $button = has_action( 'woocommerce_single_variation', 'woocommerce_single_variation_add_to_cart_button' ); if ( false !== $button ) { remove_action( 'woocommerce_single_variation', 'woocommerce_single_variation_add_to_cart_button', (int) $button ); } $held = array(); foreach ( array( 'woocommerce_before_add_to_cart_form', 'woocommerce_after_add_to_cart_form' ) as $hook ) { if ( isset( $wp_filter[ $hook ] ) ) { $held[ $hook ] = $wp_filter[ $hook ]; unset( $wp_filter[ $hook ] ); } } add_filter( 'woocommerce_available_variation', array( __CLASS__, 'quote_variation_data' ), PHP_INT_MAX ); $previous = $GLOBALS['product'] ?? null; $GLOBALS['product'] = $product; $this->quote_for = $pid; ob_start(); try { woocommerce_variable_add_to_cart(); } finally { $html = trim( (string) ob_get_clean() ); $this->quote_for = 0; $GLOBALS['product'] = $previous; remove_filter( 'woocommerce_available_variation', array( __CLASS__, 'quote_variation_data' ), PHP_INT_MAX ); foreach ( $held as $hook => $callbacks ) { $wp_filter[ $hook ] = $callbacks; } if ( false !== $button ) { add_action( 'woocommerce_single_variation', 'woocommerce_single_variation_add_to_cart_button', (int) $button ); } } if ( false === strpos( $html, '
' . $html . ''; } public static function quote_variation_data( $data ) { if ( ! is_array( $data ) ) { return $data; } unset( $data['display_price'], $data['display_regular_price'] ); $data['price_html'] = ''; return $data; } private function build_archive_css( $v ) { $css = ''; $css .= ".om-product-swatches--archive .om-variation-type-color a{" . "width:{$v('color_width_shop_archive',18)}px;height:{$v('color_height_shop_archive',18)}px;" . "border-radius:{$v('color_border_radius_shop_archive',50)}px;" . "border:{$v('color_border_size_shop_archive',1)}px solid {$v('color_border_color_shop_archive','#dedede')};" . "}"; $css .= ".om-product-swatches--archive .om-variation-type-color a:hover{" . "border:{$v('color_border_size_hover_shop_archive',2)}px solid {$v('color_border_color_hover_shop_archive','#212121')};" . "}"; $css .= ".om-product-swatches--archive .om-variation-type-color a.active{" . "border:{$v('color_border_size_selected_shop_archive',2)}px solid {$v('color_border_color_selected_shop_archive','#212121')};" . "}"; $css .= ".om-product-swatches--archive .om-variation-type-color{gap:{$v('color_gap_shop_archive',6)}px;}"; $css .= ".om-product-swatches--archive .om-variation-type-button a{" . "color:{$v('button_text_color_shop_archive','#212121')};" . "background-color:{$v('button_bg_color_shop_archive','#ffffff')};" . "border:{$v('button_border_size_shop_archive',1)}px solid {$v('button_border_color_shop_archive','#212121')};" . "border-radius:{$v('button_border_radius_shop_archive',5)}px;" . "padding:{$v('button_padding_top_shop_archive',6)}px {$v('button_padding_right_shop_archive',10)}px {$v('button_padding_bottom_shop_archive',6)}px {$v('button_padding_left_shop_archive',10)}px;" . "}"; $css .= ".om-product-swatches--archive .om-variation-type-button a:hover{" . "color:{$v('button_text_color_hover_shop_archive','#ffffff')};" . "background-color:{$v('button_bg_color_hover_shop_archive','#212121')};" . "border-color:{$v('button_border_color_hover_shop_archive','#212121')};" . "}"; $css .= ".om-product-swatches--archive .om-variation-type-button a.active{" . "color:{$v('button_text_color_selected_shop_archive','#ffffff')};" . "background-color:{$v('button_bg_color_selected_shop_archive','#212121')};" . "border-color:{$v('button_border_color_selected_shop_archive','#212121')};" . "}"; $css .= ".om-product-swatches--archive .om-variation-type-button{gap:{$v('button_gap_shop_archive',6)}px;}"; $css .= ".om-product-swatches--archive .om-variation-type-image a{" . "width:{$v('image_width_shop_archive',26)}px;height:{$v('image_height_shop_archive',26)}px;" . "border-radius:{$v('image_border_radius_shop_archive',0)}px;" . "border:{$v('image_border_size_shop_archive',1)}px solid {$v('image_border_color_shop_archive','#dedede')};" . "overflow:hidden;}"; $css .= ".om-product-swatches--archive .om-variation-type-image a img{border-radius:{$v('image_border_radius_shop_archive',0)}px;}"; $css .= ".om-product-swatches--archive .om-variation-type-image a:hover{" . "border:{$v('image_border_size_hover_shop_archive',2)}px solid {$v('image_border_color_hover_shop_archive','#212121')};" . "}"; $css .= ".om-product-swatches--archive .om-variation-type-image a.active{" . "border:{$v('image_border_size_selected_shop_archive',2)}px solid {$v('image_border_color_selected_shop_archive','#212121')};" . "}"; $css .= ".om-product-swatches--archive .om-variation-type-image{gap:{$v('image_gap_shop_archive',6)}px;}"; return $css; } private function build_recommendation_css( $v ) { $roots = array( '.related.products .om-product-swatches', '.cross-sells .om-product-swatches', '.up-sells .om-product-swatches', 'section.related .om-product-swatches', '.woocommerce-related-products .om-product-swatches', '.products.related .om-product-swatches', '.om-ppe-recently-viewed-section .om-product-swatches', '.merchant-recently-viewed-products .om-product-swatches', '.om-ppe-wishlist-page .om-product-swatches', '.om-ppe-wishlist-cart-section .om-product-swatches', ); $join = function ( $suffix ) use ( $roots ) { return implode( ',', array_map( function ( $r ) use ( $suffix ) { return $r . $suffix; }, $roots ) ); }; $css = ''; $css .= $join( ' .om-variation-type-color a' ) . "{" . "width:{$v('color_width_shop_archive',18)}px;height:{$v('color_height_shop_archive',18)}px;" . "border-radius:{$v('color_border_radius_shop_archive',50)}px;" . "border:{$v('color_border_size_shop_archive',1)}px solid {$v('color_border_color_shop_archive','#dedede')};}"; $css .= $join( ' .om-variation-type-color a:hover' ) . "{" . "border:{$v('color_border_size_hover_shop_archive',2)}px solid {$v('color_border_color_hover_shop_archive','#212121')};}"; $css .= $join( ' .om-variation-type-color a.active' ) . "{" . "border:{$v('color_border_size_selected_shop_archive',2)}px solid {$v('color_border_color_selected_shop_archive','#212121')};}"; $css .= $join( ' .om-variation-type-color' ) . "{gap:{$v('color_gap_shop_archive',6)}px;}"; $css .= $join( ' .om-variation-type-button a' ) . "{" . "color:{$v('button_text_color_shop_archive','#212121')};" . "background-color:{$v('button_bg_color_shop_archive','#ffffff')};" . "border:{$v('button_border_size_shop_archive',1)}px solid {$v('button_border_color_shop_archive','#212121')};" . "border-radius:{$v('button_border_radius_shop_archive',5)}px;" . "padding:{$v('button_padding_top_shop_archive',6)}px {$v('button_padding_right_shop_archive',10)}px {$v('button_padding_bottom_shop_archive',6)}px {$v('button_padding_left_shop_archive',10)}px;}"; $css .= $join( ' .om-variation-type-button a:hover' ) . "{" . "color:{$v('button_text_color_hover_shop_archive','#ffffff')};" . "background-color:{$v('button_bg_color_hover_shop_archive','#212121')};" . "border-color:{$v('button_border_color_hover_shop_archive','#212121')};}"; $css .= $join( ' .om-variation-type-button a.active' ) . "{" . "color:{$v('button_text_color_selected_shop_archive','#ffffff')};" . "background-color:{$v('button_bg_color_selected_shop_archive','#212121')};" . "border-color:{$v('button_border_color_selected_shop_archive','#212121')};}"; $css .= $join( ' .om-variation-type-button' ) . "{gap:{$v('button_gap_shop_archive',6)}px;}"; $css .= $join( ' .om-variation-type-image a' ) . "{" . "width:{$v('image_width_shop_archive',26)}px;height:{$v('image_height_shop_archive',26)}px;" . "border-radius:{$v('image_border_radius_shop_archive',0)}px;" . "border:{$v('image_border_size_shop_archive',1)}px solid {$v('image_border_color_shop_archive','#dedede')};" . "overflow:hidden;}"; $css .= $join( ' .om-variation-type-image a img' ) . "{border-radius:{$v('image_border_radius_shop_archive',0)}px;}"; $css .= $join( ' .om-variation-type-image a:hover' ) . "{" . "border:{$v('image_border_size_hover_shop_archive',2)}px solid {$v('image_border_color_hover_shop_archive','#212121')};}"; $css .= $join( ' .om-variation-type-image a.active' ) . "{" . "border:{$v('image_border_size_selected_shop_archive',2)}px solid {$v('image_border_color_selected_shop_archive','#212121')};}"; $css .= $join( ' .om-variation-type-image' ) . "{gap:{$v('image_gap_shop_archive',6)}px;}"; return $css; } public function output_early_multi_select_css() { if ( ! is_product() ) { return; } if ( class_exists( 'OM_Product_Swatches_Handler' ) ) { return; } $pid = (int) get_the_ID(); if ( $pid <= 0 ) { return; } if ( ! $this->multi_select_draws( $pid ) ) { return; } echo ''; } public function filter_ajax_variation_threshold( $threshold, $product ) { return (int) apply_filters( 'aieo_swatches_ajax_variation_threshold', 200, $product ); } private function swatch_mode() { if ( null === $this->swatch_mode ) { $this->swatch_mode = class_exists( 'AIEO_Theme_Adapters' ) ? (string) AIEO_Theme_Adapters::mode( 'swatches' ) : ''; } return $this->swatch_mode; } private function host_class( $slot, $type = '' ) { if ( 'inherit' !== $this->swatch_mode() || ! class_exists( 'AIEO_Theme_Adapters' ) ) { return ''; } $map = AIEO_Theme_Adapters::adapter_surface( 'swatches', 'style_map', array() ); if ( ! is_array( $map ) || empty( $map[ $slot ] ) ) { return ''; } $classes = (string) $map[ $slot ]; if ( '' !== $type && ! empty( $map[ $slot . '_type' ] ) ) { $template = (string) $map[ $slot . '_type' ]; if ( false !== strpos( $template, '%s' ) ) { $classes .= ' ' . sprintf( $template, $type ); } } return $classes; } private function host_class_suffix( $slot, $type = '' ) { $classes = $this->host_class( $slot, $type ); return '' === $classes ? '' : ' ' . $classes; } public function veto_swatch_skin( $src, $handle ) { if ( 'aieo-swatches' !== $handle ) { return $src; } return ( 'inherit' === $this->swatch_mode() ) ? false : $src; } private function resolve_attribute_type( $tax, $attribute ) { $type = isset( $tax->attribute_type ) ? (string) $tax->attribute_type : ''; if ( 'wc-visual' === $type ) { return $this->wc_visual_has_images( $this->get_attribute_taxonomy_name( $tax, $attribute ) ) ? 'image' : 'color'; } if ( ! class_exists( 'AIEO_Swatch_Type_Detector' ) ) { return $type; } $taxonomy = $this->get_attribute_taxonomy_name( $tax, $attribute ); return (string) AIEO_Swatch_Type_Detector::resolve( $type, $taxonomy ); } private function wc_visual_has_images( $taxonomy ) { if ( ! isset( $this->wc_visual_images[ $taxonomy ] ) ) { global $wpdb; $this->wc_visual_images[ $taxonomy ] = (bool) $wpdb->get_var( $wpdb->prepare( "SELECT 1 FROM {$wpdb->termmeta} tm JOIN {$wpdb->term_taxonomy} tt ON tt.term_id = tm.term_id WHERE tt.taxonomy = %s AND tm.meta_key = 'image' AND tm.meta_value NOT IN ('', '0') LIMIT 1", (string) $taxonomy ) ); } return $this->wc_visual_images[ $taxonomy ]; } private function get_attribute_taxonomy_name( $tax, $attribute ) { if ( isset( $tax->attribute_name ) && '' !== (string) $tax->attribute_name ) { return 'pa_' . (string) $tax->attribute_name; } $attribute = (string) $attribute; return 0 === strpos( $attribute, 'pa_' ) ? $attribute : 'pa_' . $attribute; } public function arbitrate_with_theme() { if ( ! class_exists( 'AIEO_Theme_Adapters' ) ) { return; } if ( AIEO_Theme_Adapters::should_stand_down( 'swatches' ) ) { $this->stand_down = true; remove_filter( 'woocommerce_dropdown_variation_attribute_options_html', array( $this, 'filter_variation_attribute_options_html' ), 10 ); return; } $this->stand_down = false; $priority = (int) AIEO_Theme_Adapters::adapter_surface( 'swatches', 'filter_priority', 10 ); if ( 10 === $priority ) { return; } remove_filter( 'woocommerce_dropdown_variation_attribute_options_html', array( $this, 'filter_variation_attribute_options_html' ), 10 ); add_filter( 'woocommerce_dropdown_variation_attribute_options_html', array( $this, 'filter_variation_attribute_options_html' ), $priority, 2 ); } public function filter_body_class( $classes ) { if ( ! is_product() ) { return $classes; } $pid = (int) get_the_ID(); if ( $pid <= 0 ) { return $classes; } if ( $this->multi_select_draws( $pid ) ) { $classes[] = 'om-multi-select-enabled'; } return $classes; } public function swatches_draw( $product_id ) { $product_id = (int) $product_id; if ( isset( $this->swatches_draw_memo[ $product_id ] ) ) { return $this->swatches_draw_memo[ $product_id ]; } $draws = false; if ( true !== $this->stand_down ) { $product = wc_get_product( $product_id ); $cache = ( $product && $product->is_type( 'variable' ) ) ? ( $this->read_cache_row( $product_id, 'single' ) ?: $this->fallback_cache( $product, 'single' ) ) : null; $ready = ! empty( $cache['variations'] ); if ( $ready ) { foreach ( array_keys( (array) $product->get_variation_attributes() ) as $attribute ) { $tax = $this->get_attribute_taxonomy( $attribute ); if ( $tax && in_array( $this->resolve_attribute_type( $tax, $attribute ), array( 'color', 'image', 'button' ), true ) ) { $draws = true; break; } } } } return $this->swatches_draw_memo[ $product_id ] = $draws; } private function multi_select_draws( $product_id ) { $ps = $this->resolve_product_settings( (int) $product_id ); return ! empty( $ps['multi_select_enabled'] ) && $this->swatches_draw( (int) $product_id ); } public function multi_select_on( $product_id ) { return $this->is_runtime_enabled() && self::cache_operational() && $this->multi_select_draws( (int) $product_id ); } private $title_line_done = false; public function title_line_draws( $product ) { if ( ! $product instanceof WC_Product || ! $product->is_type( 'variable' ) ) { return false; } if ( ! is_singular( 'product' ) || (int) get_queried_object_id() !== (int) $product->get_id() ) { return false; } $settings = $this->get_settings(); if ( empty( $settings['display_variation_name_on_product_title'] ) ) { return false; } if ( ! $this->swatches_draw( $product->get_id() ) || $this->multi_select_draws( $product->get_id() ) ) { return false; } return ! ( class_exists( 'AIEO_Catalogue_Mode' ) && AIEO_Catalogue_Mode::cart_off( $product ) ); } public function title_line_html( $product, $tag = 'span' ) { $tag = 'p' === $tag ? 'p' : 'span'; $parts = array(); $cache = $this->read_cache_row( (int) $product->get_id(), 'single' ) ?: $this->fallback_cache( $product, 'single' ); $variations = ( is_array( $cache ) && is_array( $cache['variations'] ?? null ) ) ? $cache['variations'] : array(); foreach ( (array) $product->get_variation_attributes() as $attribute => $options ) { $key = 'attribute_' . sanitize_title( (string) $attribute ); $slug = isset( $_REQUEST[ $key ] ) ? wc_clean( wp_unslash( $_REQUEST[ $key ] ) ) : $product->get_variation_default_attribute( $attribute ); if ( ! is_string( $slug ) || '' === $slug || ! in_array( $slug, (array) $options, true ) ) { continue; } $term = taxonomy_exists( (string) $attribute ) ? get_term_by( 'slug', $slug, (string) $attribute ) : false; if ( ! $term ) { $parts[] = $slug; continue; } $tax = $this->get_attribute_taxonomy( $attribute ); $swatch = $tax && in_array( $this->resolve_attribute_type( $tax, $attribute ), array( 'color', 'image', 'button' ), true ); $parts[] = $swatch ? $this->term_caption( $variations, $attribute, $term ) : (string) apply_filters( 'woocommerce_variation_option_name', $term->name, $term, $attribute, $product ); } $name = trim( (string) preg_replace( '/\s+/u', ' ', implode( ' · ', $parts ) ) ); return '<' . $tag . ' class="om-ptitle-variation-name om-ptitle-variation-name--page" data-rooster-ui="rooster-one/swatches/title" data-product="' . esc_attr( (string) $product->get_id() ) . '" aria-hidden="true">' . ( '' === $name ? ' ' : esc_html( $name ) ) . ''; } public function title_line_in_title( $content, $block = array(), $instance = null ) { if ( $this->title_line_done || is_admin() || ! is_singular( 'product' ) ) { return $content; } $level = (int) ( $block['attrs']['level'] ?? 2 ); $id = ( $instance instanceof WP_Block && isset( $instance->context['postId'] ) ) ? (int) $instance->context['postId'] : 0; if ( 1 !== $level || $id !== (int) get_queried_object_id() || ! preg_match( '~\s*$~', (string) $content ) ) { return $content; } $product = wc_get_product( $id ); if ( ! $this->title_line_draws( $product ) ) { return $content; } $this->title_line_done = true; return (string) preg_replace( '~\s*$~', $this->title_line_html( $product ) . '', (string) $content, 1 ); } public function print_title_line() { if ( $this->title_line_done || ! has_action( 'woocommerce_single_product_summary', 'woocommerce_template_single_title' ) ) { return; } $product = wc_get_product( get_queried_object_id() ); if ( ! $this->title_line_draws( $product ) ) { return; } $this->title_line_done = true; echo $this->title_line_html( $product, 'p' ); } private function term_caption( array $variations, $attribute, $term, $variation_ids = null ) { $ids = is_array( $variation_ids ) ? $variation_ids : $this->get_variation_ids_by_term( $variations, $attribute, $term->slug ); $vdesc = $this->get_variation_description_by_ids( $variations, $ids ); return ( $vdesc !== '' ) ? ( $vdesc . ' - ' . $term->name ) : $term->name; } public function maybe_remove_default_add_to_cart() { if ( is_admin() || ! is_product() ) { return; } $pid = (int) get_the_ID(); if ( $pid <= 0 ) { return; } if ( ! $this->multi_select_draws( $pid ) ) { return; } remove_action( 'woocommerce_single_variation', 'woocommerce_single_variation_add_to_cart_button', 20 ); } private function get_all_cart_variations_data() { $out = array(); if ( ! function_exists( 'WC' ) || ! WC()->cart ) { return $out; } foreach ( WC()->cart->get_cart() as $cart_item_key => $cart_item ) { $product_id = isset( $cart_item['product_id'] ) ? (int) $cart_item['product_id'] : 0; $variation_id = isset( $cart_item['variation_id'] ) ? (int) $cart_item['variation_id'] : 0; $quantity = isset( $cart_item['quantity'] ) ? (int) $cart_item['quantity'] : 1; if ( $product_id <= 0 || $variation_id <= 0 ) { continue; } if ( ! isset( $out[ $product_id ] ) ) { $out[ $product_id ] = array(); } if ( isset( $out[ $product_id ][ $variation_id ] ) ) { $out[ $product_id ][ $variation_id ]['qty'] += $quantity; continue; } $variation_name = ''; if ( isset( $cart_item['variation'] ) && is_array( $cart_item['variation'] ) ) { $parts = array(); foreach ( $cart_item['variation'] as $attr_key => $attr_value ) { if ( $attr_value === '' ) { continue; } $taxonomy = str_replace( 'attribute_', '', (string) $attr_key ); $term = get_term_by( 'slug', (string) $attr_value, $taxonomy ); $parts[] = ( $term && ! is_wp_error( $term ) ) ? (string) $term->name : ucfirst( str_replace( '-', ' ', (string) $attr_value ) ); } $variation_name = implode( ', ', $parts ); } $out[ $product_id ][ $variation_id ] = array( 'qty' => $quantity, 'name' => $variation_name, 'cart_item_key' => (string) $cart_item_key, ); } return $out; } public function output_cart_variations_data_tag() { $aieo_product_search = is_search() && ( 'product' === get_query_var( 'post_type' ) || is_post_type_archive( 'product' ) ); if ( ! is_product() && ! is_shop() && ! is_product_taxonomy() && ! $aieo_product_search ) { return; } $s = $this->get_settings(); if ( isset( $s['cart_sync_enabled'] ) && empty( $s['cart_sync_enabled'] ) ) { return; } $data = $this->get_all_cart_variations_data(); echo ''; } public function add_cart_variations_fragment( $fragments ) { $s = $this->get_settings(); if ( isset( $s['cart_sync_enabled'] ) && empty( $s['cart_sync_enabled'] ) ) { return $fragments; } if ( ! is_array( $fragments ) ) { $fragments = array(); } $data = $this->get_all_cart_variations_data(); $fragments['.om-cart-variations-data'] = ''; return $fragments; } public function output_see_all_variations_modal() { if ( class_exists( 'OM_Product_Swatches_Handler' ) ) { return; } $s = $this->get_settings(); if ( empty( $s['on_shop_catalog'] ) ) { return; } if ( ! is_shop() && ! is_product_taxonomy() && ! is_search() ) { return; } $title = self::copy_or( $s['see_all_modal_title'] ?? '', __( 'All Variations', 'ai-eshop-optimizer' ) ); $view = self::copy_or( $s['see_all_view_product_text'] ?? '', __( 'View product page', 'ai-eshop-optimizer' ) ); ?> stand_down ) { return; } if ( is_admin() ) { return; } $is_archive = is_shop() || ( function_exists( 'is_product_taxonomy' ) && is_product_taxonomy() ) || is_search(); if ( ! $is_archive ) { return; } $s = $this->get_settings(); if ( empty( $s['on_shop_catalog'] ) ) { return; } remove_action( 'woocommerce_after_shop_loop_item', 'woocommerce_template_loop_add_to_cart', 10 ); add_action( 'woocommerce_after_shop_loop_item', array( $this, 'render_archive_swatches' ), 10 ); if ( class_exists( 'AIEO_Theme_Adapters' ) && method_exists( 'AIEO_Theme_Adapters', 'draw' ) ) { AIEO_Theme_Adapters::draw( 'archive_swatches', 'woocommerce_after_shop_loop_item' ); } add_action( 'woocommerce_after_shop_loop_item', static function () { remove_action( 'woocommerce_after_shop_loop_item', 'woocommerce_template_loop_add_to_cart', 10 ); }, 9 ); } public function render_archive_swatches() { global $product; if ( ! $product instanceof WC_Product ) { return; } if ( class_exists( 'AIEO_Card', false ) && AIEO_Card::draws_block_card() ) { return; } $product_id = (int) $product->get_id(); if ( isset( self::$rendered_for_product[ $product_id ] ) ) { return; } if ( ! $product->is_type( 'variable' ) ) { self::$rendered_for_product[ $product_id ] = 'classic_loop'; woocommerce_template_loop_add_to_cart(); return; } self::$rendered_for_product[ $product_id ] = 'classic_loop'; $cache = $this->read_cache_row( $product_id, 'archive' ); if ( ! $cache ) { $cache = $this->fallback_cache( $product, 'archive' ); } if ( ! $cache || empty( $cache['variations'] ) ) { woocommerce_template_loop_add_to_cart(); return; } $this->render_archive_product_wrapper( $product, $cache ); } public function render_card_swatches_html( WC_Product $product, $context = 'archive' ) { if ( ! $this->is_runtime_enabled() ) { return ''; } if ( 'archive' === $context && class_exists( 'AIEO_Card', false ) && AIEO_Card::draws_block_card() ) { return ''; } $context_key = ( $context === 'product' ) ? 'product' : 'archive'; $product_id = (int) $product->get_id(); self::want_assets(); if ( isset( self::$rendered_for_product[ $product_id ] ) ) { return ''; } if ( ! $product->is_type( 'variable' ) || ! self::cache_operational() ) { self::$rendered_for_product[ $product_id ] = 'block'; return $this->card_cta_only_html( $product ); } self::$rendered_for_product[ $product_id ] = 'block'; $cache = $this->read_cache_row( $product_id, $context_key ); if ( ! $cache ) { $cache = $this->fallback_cache( $product, $context_key ); } if ( ! $cache || empty( $cache['variations'] ) ) { return $this->card_cta_only_html( $product ); } $previous = isset( $GLOBALS['product'] ) ? $GLOBALS['product'] : null; $GLOBALS['product'] = $product; ob_start(); $this->render_archive_product_wrapper( $product, $cache ); $html = (string) ob_get_clean(); if ( $previous === null ) { unset( $GLOBALS['product'] ); } else { $GLOBALS['product'] = $previous; } return $html; } private function card_cta_only_html( WC_Product $product ) { $previous = isset( $GLOBALS['product'] ) ? $GLOBALS['product'] : null; $GLOBALS['product'] = $product; ob_start(); echo '
'; $this->render_archive_cta_link( $product ); echo '
'; $html = (string) ob_get_clean(); if ( $previous === null ) { unset( $GLOBALS['product'] ); } else { $GLOBALS['product'] = $previous; } return $html; } public function render_card_row_html( WC_Product $product, $cap = 8 ) { if ( ! $this->is_runtime_enabled() || ! $product->is_type( 'variable' ) || ! self::cache_operational() ) { return ''; } $cache = $this->read_cache_row( (int) $product->get_id(), 'archive' ); if ( ! $cache ) { $cache = $this->fallback_cache( $product, 'archive' ); } if ( ! $cache || empty( $cache['variations'] ) ) { return ''; } $variations = (array) $cache['variations']; $attribute = ''; $tax = null; $type = ''; $options = array(); foreach ( (array) $product->get_variation_attributes() as $name => $opts ) { $t = $this->get_attribute_taxonomy( (string) $name ); if ( ! $t ) { continue; } $ty = $this->resolve_attribute_type( $t, (string) $name ); if ( 'select' === $ty ) { continue; } $attribute = (string) $name; $tax = $t; $type = $ty; $options = array_map( 'strval', (array) $opts ); break; } if ( '' === $attribute || empty( $options ) ) { return ''; } $terms = wc_get_product_terms( (int) $product->get_id(), $attribute, array( 'fields' => 'all' ) ); if ( is_wp_error( $terms ) || ! is_array( $terms ) ) { return ''; } $term_ids = array(); foreach ( $terms as $t ) { $term_ids[] = (int) $t->term_id; } if ( $term_ids && function_exists( 'update_termmeta_cache' ) ) { update_termmeta_cache( $term_ids ); } $pics = array(); foreach ( $term_ids as $tid ) { $pic = (int) get_term_meta( $tid, '_aieo_swatch_image_id', true ) ?: (int) get_term_meta( $tid, 'product_attribute_image', true ) ?: (int) get_term_meta( $tid, 'image', true ); if ( $pic > 0 ) { $pics[] = $pic; } } if ( $pics ) { update_meta_cache( 'post', array_unique( $pics ) ); } $url = (string) get_permalink( $product->get_id() ); $key = 'attribute_' . sanitize_title( $attribute ); $single = 1 === count( (array) $product->get_variation_attributes() ); $total = 0; $items = array(); $rules = array(); static $printed = array(); foreach ( $terms as $term ) { if ( ! in_array( $term->slug, $options, true ) ) { continue; } $total++; if ( count( $items ) >= max( 1, (int) $cap ) ) { continue; } $ids = $this->get_variation_ids_by_term( $variations, $attribute, $term->slug ); if ( empty( $ids ) || $this->is_term_out_of_stock( $variations, $ids ) ) { continue; } $class = 'rc-swatch'; $inner = ''; if ( 'button' === $type ) { $inner = esc_html( $term->name ); } elseif ( 'color' === $type || ( ! empty( $this->wc_visual_images[ $this->get_attribute_taxonomy_name( null, $attribute ) ] ) && (int) get_term_meta( (int) $term->term_id, 'image', true ) <= 0 && '' !== (string) get_term_meta( (int) $term->term_id, 'color', true ) ) ) { $cls = 'rs-t-' . (int) $term->term_id; $class .= ' ' . $cls; if ( ! isset( $printed[ $cls ] ) ) { $printed[ $cls ] = true; $rules[] = '.rc-swatch.' . $cls . '{' . str_replace( array( '{', '}', '<', '>' ), '', $this->resolve_term_color_style( (int) $term->term_id ) ) . '}'; } } else { list( $sw_w, $sw_h ) = class_exists( 'AIEO_Swatch_Sizes' ) ? AIEO_Swatch_Sizes::dims( 'om_swatch_archive_2x' ) : array( 64, 64 ); $img = $this->resolve_term_image_src( (int) $term->term_id, $variations, $attribute, $term->slug, $sw_w ?: 64, $sw_h ?: 64, true ); if ( ! empty( $img['url'] ) ) { $cls = 'rs-i-' . substr( md5( (string) $img['url'] ), 0, 10 ); $class .= ' ' . $cls; if ( ! isset( $printed[ $cls ] ) ) { $printed[ $cls ] = true; $rules[] = '.rc-swatch.' . $cls . '{--rc-sw:url("' . esc_url( (string) $img['url'] ) . '")}'; } } } $items[] = array( 'class' => $class, 'href' => add_query_arg( $key, rawurlencode( $term->slug ), $url ), 'name' => $term->name, 'vid' => ( $single && 1 === count( $ids ) ) ? (int) reset( $ids ) : 0, 'inner' => $inner, ); } if ( empty( $items ) ) { return ''; } $shown = count( $items ); $capacities = array( 3, 4, 5, 6, 7, 8 ); $rest = ''; foreach ( $capacities as $k ) { $fits = ( $total <= $k && $shown >= $total ); $left = $fits ? 0 : $total - min( $shown, $k - 1 ); $rest .= ' data-rest-' . $k . '="' . ( $left > 0 ? '+' . $left : '' ) . '"'; } $pickable = $total <= max( $capacities ) && $shown >= $total; foreach ( $items as $i => $it ) { $items[ $i ] = sprintf( '%6$s', esc_attr( $it['class'] ), esc_url( $it['href'] ), ( $pickable && $it['vid'] ) ? ' role="button" aria-pressed="false"' : '', esc_attr( $it['name'] ), $it['vid'] ? ' data-variation-id="' . (int) $it['vid'] . '"' : '', $it['inner'] ); } $more = sprintf( '', esc_url( $url ), $rest, esc_attr( sprintf( __( 'See all %d variations', 'ai-eshop-optimizer' ), $total ) ) ); $style = $rules ? '' : ''; return $style . sprintf( '
%4$s%5$s
', 'button' === $type ? ' rc-swatches--text' : '', esc_attr( $type ), esc_attr( wc_attribute_label( $attribute, $product ) ), implode( '', $items ), $more, (int) $product->get_id(), (int) $total, count( (array) $product->get_children() ) ); } public function card_list_rows( WC_Product $product ) { $out = array( 'rows' => array(), 'styles' => '' ); if ( ! $product->is_type( 'variable' ) ) { return $out; } $card_attr = ''; foreach ( array_keys( (array) $product->get_variation_attributes() ) as $name ) { $t = $this->get_attribute_taxonomy( (string) $name ); if ( $t && 'select' !== $this->resolve_attribute_type( $t, (string) $name ) ) { $card_attr = (string) $name; break; } } $card_type = ''; if ( '' !== $card_attr ) { $card_type = $this->resolve_attribute_type( $this->get_attribute_taxonomy( $card_attr ), $card_attr ); } $in_cart = array(); if ( function_exists( 'WC' ) && WC()->cart ) { foreach ( WC()->cart->get_cart() as $item ) { $vid = isset( $item['variation_id'] ) ? (int) $item['variation_id'] : 0; if ( $vid > 0 ) { $in_cart[ $vid ] = ( $in_cart[ $vid ] ?? 0 ) + (int) $item['quantity']; } } } $ids = array_map( 'intval', (array) $product->get_children() ); if ( $ids && function_exists( '_prime_post_caches' ) ) { _prime_post_caches( $ids ); } $rules = array(); $no_price = class_exists( 'AIEO_Catalogue_Mode' ) && AIEO_Catalogue_Mode::prices_off( $product ); $ask = class_exists( 'AIEO_Product_Request_Settings' ) && method_exists( 'AIEO_Product_Request_Settings', 'quote_only' ) && AIEO_Product_Request_Settings::quote_only( $product ); foreach ( $ids as $vid ) { $v = wc_get_product( $vid ); if ( ! $v instanceof WC_Product_Variation || ( $ask ? 'publish' !== $v->get_status() : ! $v->variation_is_visible() ) ) { continue; } $names = array(); $cls = ''; foreach ( (array) $v->get_variation_attributes() as $key => $slug ) { $tax = substr( (string) $key, 10 ); $term = ( '' !== (string) $slug && taxonomy_exists( $tax ) ) ? get_term_by( 'slug', (string) $slug, $tax ) : null; $names[] = $term ? $term->name : (string) $slug; if ( $term && '' === $cls && sanitize_title( $card_attr ) === sanitize_title( $tax ) && 'button' !== $card_type ) { if ( 'color' === $card_type || ( (int) get_term_meta( (int) $term->term_id, 'image', true ) <= 0 && '' !== (string) get_term_meta( (int) $term->term_id, 'color', true ) && ! empty( $this->wc_visual_images[ $this->get_attribute_taxonomy_name( null, $card_attr ) ] ) ) ) { $cls = 'rs-t-' . (int) $term->term_id; $rules[ $cls ] = '.rc-swatch.' . $cls . '{' . str_replace( array( '{', '}', '<', '>' ), '', $this->resolve_term_color_style( (int) $term->term_id ) ) . '}'; } else { list( $sw_w, $sw_h ) = class_exists( 'AIEO_Swatch_Sizes' ) ? AIEO_Swatch_Sizes::dims( 'om_swatch_product_2x' ) : array( 150, 150 ); $img = $this->resolve_term_image_src( (int) $term->term_id, array(), $card_attr, $term->slug, $sw_w ?: 150, $sw_h ?: 150, false ); if ( empty( $img['id'] ) || empty( $img['url'] ) ) { continue; } $cls = 'rs-i-' . substr( md5( (string) $img['url'] ), 0, 10 ); $rules[ $cls ] = '.rc-swatch.' . $cls . '{--rc-sw:url("' . esc_url( (string) $img['url'] ) . '")}'; } } } if ( '' === $cls && $v->get_image_id() ) { $src = wp_get_attachment_image_src( (int) $v->get_image_id(), 'thumbnail' ); if ( is_array( $src ) && ! empty( $src[0] ) ) { $cls = 'rs-i-' . substr( md5( (string) $src[0] ), 0, 10 ); $rules[ $cls ] = '.rc-swatch.' . $cls . '{--rc-sw:url("' . esc_url( (string) $src[0] ) . '")}'; } } $buys = $v->is_purchasable(); $max = $buys ? $v->get_max_purchase_quantity() : 0; $out['rows'][] = array( 'id' => (int) $vid, 'name' => implode( ' · ', array_filter( $names, 'strlen' ) ), 'code' => (string) $v->get_sku(), 'price' => $no_price ? 0.0 : (float) wc_get_price_to_display( $v ), 'html' => ( $ask && $no_price ) ? '' : (string) $v->get_price_html(), 'stock' => $buys && $v->is_in_stock(), 'max' => ( $max > 0 ) ? (int) $max : 0, 'in_cart' => (int) ( $in_cart[ $vid ] ?? 0 ), 'sw' => $cls, 'ask' => $ask, ); } $out['styles'] = implode( '', $rules ); return $out; } private function is_one_to_one_variation_mapping( WC_Product $product ) { $pid = (int) $product->get_id(); if ( isset( $this->one_to_one_memo[ $pid ] ) ) { return $this->one_to_one_memo[ $pid ]; } $variation_ids = $product->get_children(); $vcount = count( $variation_ids ); if ( $vcount < 2 ) { return $this->one_to_one_memo[ $pid ] = false; } $attrs = $product->get_attributes(); $var_attr_keys = array(); foreach ( $attrs as $key => $attr ) { if ( is_object( $attr ) && method_exists( $attr, 'get_variation' ) && $attr->get_variation() ) { $var_attr_keys[] = (string) $key; } } if ( count( $var_attr_keys ) < 2 ) { return $this->one_to_one_memo[ $pid ] = false; } $distinct = array_fill_keys( $var_attr_keys, array() ); foreach ( $variation_ids as $vid ) { $v = wc_get_product( $vid ); if ( ! $v ) { continue; } $va = $v->get_attributes(); foreach ( $var_attr_keys as $k ) { $val = isset( $va[ $k ] ) ? (string) $va[ $k ] : ''; if ( $val !== '' ) { $distinct[ $k ][ $val ] = true; } } } foreach ( $distinct as $values ) { if ( count( $values ) !== $vcount ) { return $this->one_to_one_memo[ $pid ] = false; } } return $this->one_to_one_memo[ $pid ] = true; } private function render_archive_product_wrapper( WC_Product $product, array $cache ) { $product_id = (int) $product->get_id(); $button_select_text = (string) $product->add_to_cart_text(); $button_add_text = (string) $product->single_add_to_cart_text(); if ( $button_select_text === $button_add_text ) { $button_add_text = __( 'Add to cart', 'woocommerce' ); } remove_action( 'woocommerce_single_variation', 'woocommerce_single_variation_add_to_cart_button', 20 ); add_filter( 'woocommerce_reset_variations_link', '__return_empty_string' ); $collapse = $this->is_one_to_one_variation_mapping( $product ); $collapse_attr = $collapse ? ' data-aieo-collapse-secondary="1"' : ''; $lean = (bool) apply_filters( 'aieo_card_swatches_lean', true, $product ); $chips = ''; if ( $lean ) { $defaults = (array) $product->get_default_attributes(); $i = 0; $rows = ''; foreach ( (array) $product->get_variation_attributes() as $attribute => $options ) { if ( $collapse && $i > 0 ) { break; } $key = sanitize_title( (string) $attribute ); $selected = isset( $defaults[ $key ] ) ? (string) $defaults[ $key ] : ''; ob_start(); $this->render_attribute_wrapper( $product, $cache, (string) $attribute, (array) $options, $selected, 'archive' ); $row = trim( (string) ob_get_clean() ); if ( '' !== $row ) { $rows .= '' . $row . ''; } $i++; } if ( '' !== $rows ) { $chips = '' . $rows . ''; } } printf( '
', esc_attr( $button_select_text ), esc_attr( $button_add_text ), $collapse_attr, ( $lean && '' !== $chips ) ? ' data-aieo-lazy-form="1"' : '' ); if ( $lean ) { echo $chips; $this->render_archive_cta_link( $product ); if ( '' !== $chips ) { wp_enqueue_script( 'wc-add-to-cart-variation' ); printf( '', esc_url( self::card_form_url( $product_id ) ) ); } } else { ob_start(); add_filter( 'woocommerce_available_variation', array( __CLASS__, 'card_variation_data' ), PHP_INT_MAX ); woocommerce_template_single_add_to_cart(); remove_filter( 'woocommerce_available_variation', array( __CLASS__, 'card_variation_data' ), PHP_INT_MAX ); echo trim( ob_get_clean() ); $this->render_archive_cta_link( $product ); } remove_filter( 'woocommerce_reset_variations_link', '__return_empty_string' ); echo '
'; } public static function card_variation_data( $data ) { if ( ! is_array( $data ) ) { return $data; } unset( $data['gallery_images_html'], $data['gallery_image_ids'], $data['dimensions'], $data['dimensions_html'], $data['weight'], $data['weight_html'] ); if ( isset( $data['image'] ) && is_array( $data['image'] ) ) { $data['image'] = array_intersect_key( $data['image'], array_flip( array( 'src', 'srcset', 'sizes', 'alt', 'title', 'src_w', 'src_h', 'thumb_src' ) ) ); } return $data; } public static function keep_card_script( $handles ) { $handles = (array) $handles; $handles[] = 'aieo-card-swatches'; return $handles; } public static function card_form_url( $product_id ) { return add_query_arg( 'product_id', (int) $product_id, WC_AJAX::get_endpoint( 'aieo_card_form' ) ); } private function card_form_html( WC_Product $product ) { $previous = isset( $GLOBALS['product'] ) ? $GLOBALS['product'] : null; $GLOBALS['product'] = $product; remove_action( 'woocommerce_single_variation', 'woocommerce_single_variation_add_to_cart_button', 20 ); add_filter( 'woocommerce_reset_variations_link', '__return_empty_string' ); add_filter( 'woocommerce_available_variation', array( __CLASS__, 'card_variation_data' ), PHP_INT_MAX ); ob_start(); woocommerce_template_single_add_to_cart(); $this->render_archive_cta_link( $product ); $form = trim( (string) ob_get_clean() ); remove_filter( 'woocommerce_available_variation', array( __CLASS__, 'card_variation_data' ), PHP_INT_MAX ); remove_filter( 'woocommerce_reset_variations_link', '__return_empty_string' ); if ( null === $previous ) { unset( $GLOBALS['product'] ); } else { $GLOBALS['product'] = $previous; } return $form; } public function ajax_card_form() { $product = wc_get_product( isset( $_GET['product_id'] ) ? absint( wp_unslash( $_GET['product_id'] ) ) : 0 ); if ( ! $product instanceof WC_Product || ! $product->is_type( 'variable' ) || 'publish' !== $product->get_status() || ! $product->is_visible() ) { wp_die( '', '', array( 'response' => 404 ) ); } echo $this->card_form_html( $product ); } private function render_archive_cta_link( WC_Product $product ) { $product_id = (int) $product->get_id(); $add_to_cart_text = (string) $product->add_to_cart_text(); $add_to_cart_url = (string) $product->add_to_cart_url(); $classes = array( 'button', 'om-add-to-cart-button', 'add_to_cart_button', 'aieo-archive-add-to-cart' ); $is_in_cart = $this->is_product_in_cart( $product_id ); $is_variable = $product->is_type( 'variable' ); $has_defaults = ! empty( $product->get_default_attributes() ); $is_see_options = $is_variable && ! $has_defaults && ! $is_in_cart; $extra_attrs = ''; if ( $is_in_cart ) { $classes[] = 'aieo-archive-add-to-cart--in-cart'; $extra_attrs = ' aria-disabled="true" tabindex="-1"'; } elseif ( $is_see_options ) { $classes[] = 'aieo-archive-add-to-cart--see-options'; } if ( $product->is_purchasable() && $product->is_in_stock() && $product->supports( 'ajax_add_to_cart' ) ) { $classes[] = 'ajax_add_to_cart'; } if ( $has_defaults ) { $add_to_cart_text = (string) $product->single_add_to_cart_text(); } $add_to_cart_class = implode( ' ', $classes ); printf( '%5$s', esc_url( $add_to_cart_url ), esc_attr( $add_to_cart_class ), $product_id, esc_attr( (string) $product->get_sku() ), esc_html( $add_to_cart_text ), $extra_attrs ); } private function is_product_in_cart( $product_id ) { if ( ! function_exists( 'WC' ) ) { return false; } $cart = WC()->cart; if ( ! $cart ) { return false; } $pid = (int) $product_id; if ( $pid <= 0 ) { return false; } foreach ( $cart->get_cart() as $item ) { $ipid = (int) ( $item['product_id'] ?? 0 ); $ivid = (int) ( $item['variation_id'] ?? 0 ); if ( ( $ipid === $pid || $ivid === $pid ) && (int) ( $item['quantity'] ?? 0 ) > 0 ) { return true; } } return false; } public function filter_variation_attribute_options_html( $html, $args ) { $args = wp_parse_args( $args, array( 'options' => false, 'attribute' => false, 'product' => false, 'selected' => false, 'name' => '', 'id' => '', 'class' => '', 'show_option_none' => '', ) ); $product = $args['product']; $attribute = (string) $args['attribute']; if ( ! $product instanceof WC_Product || $attribute === '' ) { return $html; } if ( false === $args['selected'] ) { $selected_key = 'attribute_' . sanitize_title( $attribute ); $args['selected'] = isset( $_REQUEST[ $selected_key ] ) ? wc_clean( wp_unslash( $_REQUEST[ $selected_key ] ) ) : $product->get_variation_default_attribute( $attribute ); } $tax = $this->get_attribute_taxonomy( $attribute ); if ( ! $tax ) { return $html; } $attribute_type = $this->resolve_attribute_type( $tax, $attribute ); if ( $attribute_type === 'select' ) { $classes = array( 'om-variation-item' ); if ( ! empty( $args['class'] ) ) { $classes = array_merge( $classes, array_filter( explode( ' ', (string) $args['class'] ) ) ); } if ( ! empty( $args['selected'] ) ) { $classes[] = 'active'; } $replaced = str_replace( ''; } private static function pressed_attr( $pressed ) { return ' aria-pressed="' . ( $pressed ? 'true' : 'false' ) . '"'; } private static function state_attrs( $state, $title_label ) { if ( 'sold_out' === $state ) { return ' aria-disabled="true"'; } return 'backorder' === $state ? ' aria-label="' . esc_attr( $title_label ) . '"' : ''; } private function is_term_on_backorder( array $variations, array $variation_ids ) { $lookup = array_flip( array_map( 'intval', $variation_ids ) ); $backorder = false; foreach ( $variations as $v ) { if ( ! isset( $lookup[ (int) ( $v['variation_id'] ?? 0 ) ] ) ) { continue; } $status = (string) ( $v['stock_status'] ?? '' ); if ( 'onbackorder' === $status ) { $backorder = true; } elseif ( 'outofstock' !== $status ) { return false; } } return $backorder; } private function is_term_out_of_stock( array $variations, array $variation_ids ) { if ( empty( $variation_ids ) ) { return true; } $lookup = array_flip( array_map( 'intval', $variation_ids ) ); foreach ( $variations as $v ) { $vid = (int) ( $v['variation_id'] ?? 0 ); if ( ! isset( $lookup[ $vid ] ) ) { continue; } if ( ! empty( $v['available'] ) ) { return false; } } return true; } private function resolve_product_settings( $product_id ) { $product_id = (int) $product_id; if ( isset( $this->product_settings_cache[ $product_id ] ) ) { return $this->product_settings_cache[ $product_id ]; } $g = $this->get_settings(); $global = array( 'display_style' => isset( $g['multi_select_display_style'] ) ? (string) $g['multi_select_display_style'] : 'grid', 'multi_select_enabled' => ! empty( $g['multi_select_enabled'] ), 'show_quantity' => ! empty( $g['multi_select_show_quantity'] ), 'cart_sync_enabled' => ! isset( $g['cart_sync_enabled'] ) || ! empty( $g['cart_sync_enabled'] ), 'source' => 'global', ); if ( get_post_type( $product_id ) !== 'product' ) { if ( $global['display_style'] !== 'dropdown' ) { $global['show_quantity'] = false; } $this->product_settings_cache[ $product_id ] = $global; return $global; } $type = class_exists( 'WC_Product_Factory' ) ? WC_Product_Factory::get_product_type( $product_id ) : ''; if ( $type !== 'variable' ) { if ( $global['display_style'] !== 'dropdown' ) { $global['show_quantity'] = false; } $this->product_settings_cache[ $product_id ] = $global; return $global; } $resolved = $global; $display_style_source = 'global'; $show_quantity_source = 'global'; $p_display = get_post_meta( $product_id, '_om_swatch_display_style', true ); $p_multi = get_post_meta( $product_id, '_om_swatch_multi_select', true ); $p_qty = get_post_meta( $product_id, '_om_swatch_show_quantity', true ); if ( $p_display === 'grid' || $p_display === 'dropdown' ) { $resolved['display_style'] = $p_display; $resolved['source'] = 'product'; $display_style_source = 'product'; } if ( $p_multi === 'yes' ) { $resolved['multi_select_enabled'] = true; $resolved['source'] = 'product'; } elseif ( $p_multi === 'no' ) { $resolved['multi_select_enabled'] = false; $resolved['source'] = 'product'; } if ( $p_qty === 'yes' ) { $resolved['show_quantity'] = true; $show_quantity_source = 'product'; } elseif ( $p_qty === 'no' ) { $resolved['show_quantity'] = false; $show_quantity_source = 'product'; } if ( $display_style_source === 'global' ) { $dropdown_cats = isset( $g['dropdown_style_categories'] ) ? (array) $g['dropdown_style_categories'] : array(); $grid_cats = isset( $g['grid_style_categories'] ) ? (array) $g['grid_style_categories'] : array(); if ( ! empty( $dropdown_cats ) || ! empty( $grid_cats ) ) { $product_cats = wc_get_product_cat_ids( $product_id ); if ( ! empty( $product_cats ) ) { if ( ! empty( $dropdown_cats ) && array_intersect( $product_cats, $dropdown_cats ) ) { $resolved['display_style'] = 'dropdown'; $resolved['source'] = 'category'; $display_style_source = 'category'; } if ( ! empty( $grid_cats ) && array_intersect( $product_cats, $grid_cats ) ) { $resolved['display_style'] = 'grid'; $resolved['source'] = 'category'; $display_style_source = 'category'; } } } } if ( $resolved['source'] !== 'product' && $resolved['multi_select_enabled'] ) { $ms_cats = isset( $g['multi_select_categories'] ) ? (array) $g['multi_select_categories'] : array(); if ( ! empty( $ms_cats ) ) { $product_cats = wc_get_product_cat_ids( $product_id ); if ( empty( $product_cats ) || ! array_intersect( $product_cats, $ms_cats ) ) { $resolved['multi_select_enabled'] = false; } } } if ( $show_quantity_source === 'global' && $resolved['show_quantity'] && $resolved['display_style'] === 'dropdown' ) { $q_cats = isset( $g['quantity_selector_categories'] ) ? (array) $g['quantity_selector_categories'] : array(); if ( ! empty( $q_cats ) ) { $product_cats = wc_get_product_cat_ids( $product_id ); if ( empty( $product_cats ) || ! array_intersect( $product_cats, $q_cats ) ) { $resolved['show_quantity'] = false; } } } if ( $resolved['display_style'] !== 'dropdown' ) { $resolved['show_quantity'] = false; } $this->product_settings_cache[ $product_id ] = $resolved; return $resolved; } public function product_settings( $product_id ) { return $this->resolve_product_settings( $product_id ); } public function row_layout() { $g = $this->get_settings(); $parts = array( 'check', 'image', 'name', 'price', 'qty' ); $order = array_values( array_intersect( array_map( 'strval', (array) ( $g['multi_select_row_order'] ?? array() ) ), $parts ) ); return array( 'order' => array_values( array_unique( array_merge( $order, $parts ) ) ), 'image' => ! isset( $g['multi_select_row_image'] ) || ! empty( $g['multi_select_row_image'] ), 'price' => ! isset( $g['multi_select_row_price'] ) || ! empty( $g['multi_select_row_price'] ), 'qty' => ! isset( $g['multi_select_row_qty'] ) || ! empty( $g['multi_select_row_qty'] ), ); } private function get_attribute_taxonomy( $attribute_name ) { $name = (string) $attribute_name; $key = sanitize_title( $name ); if ( array_key_exists( $key, $this->taxonomy_cache ) ) { return $this->taxonomy_cache[ $key ] ?: null; } $all = function_exists( 'wc_get_attribute_taxonomies' ) ? wc_get_attribute_taxonomies() : array(); $short = str_replace( 'pa_', '', $name ); foreach ( $all as $tax ) { if ( isset( $tax->attribute_name ) && (string) $tax->attribute_name === $short ) { $this->taxonomy_cache[ $key ] = $tax; return $tax; } } $this->taxonomy_cache[ $key ] = false; return null; } private function encode_attribute_slug( $attribute ) { $s = sanitize_title( (string) $attribute ); return $s; } private function read_cache_row( $product_id, $context ) { $memo_key = ( (int) $product_id ) . '|' . (string) $context; if ( array_key_exists( $memo_key, $this->row_cache ) ) { return $this->row_cache[ $memo_key ]; } if ( ! $this->swatch_table_ready( 'aieo_dmm_swatches_cache' ) ) { $this->row_cache[ $memo_key ] = null; return null; } global $wpdb; $table = $wpdb->prefix . 'aieo_dmm_swatches_cache'; $row = $wpdb->get_row( $wpdb->prepare( "SELECT variations_json, variation_ids_json, variations_map_json, total_available, stored_count, cache_hash FROM {$table} WHERE product_id = %d AND context = %s LIMIT 1", (int) $product_id, (string) $context ), ARRAY_A ); if ( ! $row ) { if ( '' !== (string) $wpdb->last_error && false !== stripos( (string) $wpdb->last_error, "doesn't exist" ) ) { update_option( self::CACHE_READY_OPTION, 'no', true ); if ( function_exists( 'aieo_dmm_forget_known_tables' ) ) { aieo_dmm_forget_known_tables(); } } $this->row_cache[ $memo_key ] = null; return null; } $decoded = $this->decode_cache_row( (int) $product_id, $row ); $this->row_cache[ $memo_key ] = $decoded; return $decoded; } private function decode_cache_row( $product_id, array $row ) { $current_hash = $this->get_settings_hash_cached(); if ( $current_hash !== '' && $row['cache_hash'] !== '' && $row['cache_hash'] !== $current_hash ) { $this->pending_rebuilds[ (int) $product_id ] = true; } $variations = json_decode( (string) $row['variations_json'], true ); $ids = json_decode( (string) $row['variation_ids_json'], true ); $map = json_decode( (string) $row['variations_map_json'], true ); return array( 'variations' => is_array( $variations ) ? $variations : array(), 'ids' => is_array( $ids ) ? $ids : array(), 'map' => is_array( $map ) ? $map : array(), 'total_available' => (int) $row['total_available'], 'stored_count' => (int) $row['stored_count'], ); } public function prime_card_loop( $query ) { if ( is_admin() || ! ( $query instanceof WP_Query ) ) { return; } $typed = in_array( 'product', (array) $query->get( 'post_type' ), true ); $ids = array(); foreach ( (array) $query->posts as $p ) { if ( is_object( $p ) ? 'product' !== $p->post_type : ! $typed ) { continue; } $ids[] = is_object( $p ) ? (int) $p->ID : (int) $p; } if ( count( $ids ) < 2 ) { return; } $this->prime_archive_rows( $query ); update_object_term_cache( $ids, 'product' ); update_postmeta_cache( $ids ); $pictures = array(); foreach ( $ids as $id ) { $pictures[] = (int) get_post_meta( $id, '_thumbnail_id', true ); } $variable = array(); foreach ( $ids as $id ) { $types = get_object_term_cache( $id, 'product_type' ); if ( is_array( $types ) && $types && 'variable' === $types[0]->slug ) { $variable[] = $id; } } if ( $variable ) { if ( ! wp_using_ext_object_cache() ) { $keys = array(); foreach ( $variable as $id ) { foreach ( array( 'wc_var_prices_', 'wc_product_children_', 'wc_child_has_weight_', 'wc_child_has_dimensions_', 'wc_related_' ) as $name ) { $keys[] = '_transient_' . $name . $id; $keys[] = '_transient_timeout_' . $name . $id; } } wp_prime_option_caches( $keys ); } $children = array(); foreach ( $variable as $id ) { $known = get_transient( 'wc_product_children_' . $id ); if ( is_array( $known ) && ! empty( $known['all'] ) ) { $children = array_merge( $children, array_map( 'intval', (array) $known['all'] ) ); } } if ( $children ) { _prime_post_caches( array_values( array_unique( $children ) ), true, true ); } $term_ids = array(); foreach ( $variable as $id ) { foreach ( get_object_taxonomies( 'product' ) as $tax ) { if ( 0 !== strpos( $tax, 'pa_' ) ) { continue; } $terms = get_object_term_cache( $id, $tax ); if ( is_array( $terms ) ) { foreach ( $terms as $t ) { $term_ids[] = (int) $t->term_id; } } } } if ( $term_ids ) { $term_ids = array_values( array_unique( $term_ids ) ); update_termmeta_cache( $term_ids ); foreach ( $term_ids as $tid ) { $pictures[] = (int) get_term_meta( $tid, '_aieo_swatch_image_id', true ) ?: (int) get_term_meta( $tid, 'product_attribute_image', true ) ?: (int) get_term_meta( $tid, 'image', true ); } } } $pictures = array_values( array_unique( array_filter( $pictures ) ) ); if ( $pictures ) { _prime_post_caches( $pictures, false, true ); } } public function prime_archive_rows( $query ) { if ( is_admin() || ! ( $query instanceof WP_Query ) ) { return; } $typed = in_array( 'product', (array) $query->get( 'post_type' ), true ); $ids = array(); foreach ( (array) $query->posts as $p ) { if ( is_object( $p ) ? 'product' !== $p->post_type : ! $typed ) { continue; } $pid = is_object( $p ) ? (int) $p->ID : (int) $p; if ( $pid > 0 && ! array_key_exists( $pid . '|archive', $this->row_cache ) ) { $ids[] = $pid; } } if ( count( $ids ) < 2 || ! $this->swatch_table_ready( 'aieo_dmm_swatches_cache' ) ) { return; } global $wpdb; $table = $wpdb->prefix . 'aieo_dmm_swatches_cache'; $rows = $wpdb->get_results( "SELECT product_id, variations_json, variation_ids_json, variations_map_json, total_available, stored_count, cache_hash FROM {$table} WHERE context = 'archive' AND product_id IN (" . implode( ',', array_map( 'intval', $ids ) ) . ')', ARRAY_A ); foreach ( (array) $rows as $row ) { $this->row_cache[ (int) $row['product_id'] . '|archive' ] = $this->decode_cache_row( (int) $row['product_id'], $row ); } } private function swatch_table_ready( $table ) { if ( ! isset( $this->tables_ready[ $table ] ) ) { global $wpdb; $this->tables_ready[ $table ] = ! function_exists( 'aieo_dmm_table_exists' ) || aieo_dmm_table_exists( $wpdb->prefix . $table ); } return $this->tables_ready[ $table ]; } private function get_settings_hash_cached() { if ( $this->settings === null ) { $this->get_settings(); } return $this->settings_hash; } private function fallback_cache( WC_Product $product, $context ) { $this->pending_rebuilds[ (int) $product->get_id() ] = true; $has_runner = function_exists( 'as_enqueue_async_action' ) || function_exists( 'as_schedule_single_action' ); if ( $has_runner && ! apply_filters( 'aieo_swatches_build_inline', false, $product, $context ) ) { return null; } $built = $this->build_inline_fallback( $product, $context ); $this->row_cache[ ( (int) $product->get_id() ) . '|' . (string) $context ] = $built; return $built; } private function build_inline_fallback( WC_Product $product, $context ) { $s = $this->get_settings(); $hide_oos = get_option( 'woocommerce_hide_out_of_stock_items', 'no' ) === 'yes'; $img_w = $context === 'archive' ? (int) ( $s['image_width_shop_archive'] ?? 26 ) : (int) ( $s['image_width'] ?? 72 ); $img_h = $context === 'archive' ? (int) ( $s['image_height_shop_archive'] ?? 26 ) : (int) ( $s['image_height'] ?? 72 ); $max_limit = $context === 'archive' ? max( (int) ( $s['image_swatch_limit_desktop'] ?? 5 ), (int) ( $s['image_swatch_limit_mobile'] ?? 3 ) ) : 0; $all_ids = $product->get_children(); if ( ! is_array( $all_ids ) ) { $all_ids = array(); } $to_process = array(); $total_available = 0; if ( $context === 'archive' && $hide_oos ) { foreach ( $all_ids as $vid ) { $stock = get_post_meta( (int) $vid, '_stock_status', true ); if ( $stock === 'instock' || $stock === 'onbackorder' ) { $to_process[] = (int) $vid; } } $total_available = count( $to_process ); } else { $to_process = array_map( 'intval', $all_ids ); $total_available = count( $to_process ); } $variations = array(); $map = array(); $parent_image_id = (int) $product->get_image_id(); $count = 0; foreach ( $to_process as $vid ) { if ( $context === 'archive' && $max_limit > 0 && $count >= $max_limit ) { break; } $meta = get_post_meta( (int) $vid ); if ( ! is_array( $meta ) ) { continue; } $attributes = array(); foreach ( $meta as $mkey => $mval ) { if ( strpos( $mkey, 'attribute_' ) === 0 ) { $attributes[ $mkey ] = isset( $mval[0] ) ? wc_clean( $mval[0] ) : ''; } } $stock_status = isset( $meta['_stock_status'][0] ) ? (string) $meta['_stock_status'][0] : ''; $image_id = isset( $meta['_thumbnail_id'][0] ) ? (int) $meta['_thumbnail_id'][0] : $parent_image_id; $img_url = ''; if ( $image_id > 0 ) { $inter = image_get_intermediate_size( $image_id, array( $img_w, $img_h ) ); if ( is_array( $inter ) && ! empty( $inter['url'] ) ) { $img_url = (string) $inter['url']; } else { $full = wp_get_attachment_image_src( $image_id, 'full' ); $img_url = is_array( $full ) ? (string) $full[0] : ''; } } $vdesc = isset( $meta['_variation_description'][0] ) ? trim( wp_strip_all_tags( (string) $meta['_variation_description'][0] ) ) : ''; $swatch_label = class_exists( 'AIEO_DMM_Swatches' ) ? AIEO_DMM_Swatches::compose_swatch_label( $vdesc, $attributes ) : $vdesc; $variations[] = array( 'variation_id' => (int) $vid, 'available' => ( $stock_status === 'instock' || $stock_status === 'onbackorder' ), 'img_url' => $img_url, 'img_w' => $img_w, 'img_h' => $img_h, 'attributes' => $attributes, 'price_html' => '', 'sku' => isset( $meta['_sku'][0] ) ? (string) $meta['_sku'][0] : '', 'stock_status' => $stock_status, 'variation_description' => $vdesc, 'swatch_label' => $swatch_label, ); $map[ sha1( (string) wp_json_encode( $attributes ) ) ] = (int) $vid; $count++; } return array( 'variations' => $variations, 'ids' => array_column( $variations, 'variation_id' ), 'map' => $map, 'total_available' => $total_available, 'stored_count' => count( $variations ), ); } public function filter_cart_item_thumbnail( $thumbnail, $cart_item, $cart_item_key ) { $s = $this->get_settings(); $mode = (string) ( $s['cart_variation_image'] ?? 'swatch' ); if ( $mode === 'product' ) { return $thumbnail; } $variation_id = isset( $cart_item['variation_id'] ) ? (int) $cart_item['variation_id'] : 0; if ( $variation_id <= 0 ) { return $thumbnail; } if ( ! $this->swatch_table_ready( 'aieo_dmm_swatches_variation_map' ) ) { return $thumbnail; } global $wpdb; $table = $wpdb->prefix . 'aieo_dmm_swatches_variation_map'; $row = $wpdb->get_row( $wpdb->prepare( "SELECT swatch_image_url, image_url FROM {$table} WHERE variation_id = %d LIMIT 1", $variation_id ), ARRAY_A ); $url = ''; if ( $mode === 'swatch' ) { $url = is_array( $row ) ? (string) ( $row['swatch_image_url'] ?? '' ) : ''; if ( $url === '' && is_array( $row ) ) { $url = (string) ( $row['image_url'] ?? '' ); } } elseif ( $mode === 'variation' ) { $url = is_array( $row ) ? (string) ( $row['image_url'] ?? '' ) : ''; } if ( $url === '' ) { return $thumbnail; } return sprintf( '', esc_url( $url ) ); } public function flush_pending_rebuilds() { if ( empty( $this->pending_rebuilds ) || ! $this->swatch_table_ready( 'aieo_dmm_swatches_cache' ) ) { return; } $ids = array_keys( $this->pending_rebuilds ); $this->pending_rebuilds = array(); if ( function_exists( 'as_enqueue_async_action' ) ) { as_enqueue_async_action( 'aieo_dmm_swatches_rebuild_batch', array( $ids ), 'aieo-dmm-swatches', true ); } else { wp_schedule_single_event( time() + 1, 'aieo_dmm_swatches_rebuild_batch', array( $ids ) ); } } private static function copy_or($value, $fallback) { $value = trim((string) $value); return '' !== $value ? $value : (string) $fallback; } }