callbacks as $prio => $cbs) { foreach ($cbs as $cb) { $name = self::callback_name($cb['function']); foreach ($ours as $needle) { if (false !== stripos($name, $needle)) { $drop[] = array($cb['function'], $prio); break; } } } } foreach ($drop as $d) { remove_action($hook, $d[0], $d[1]); } if (!has_action($hook, 'woocommerce_output_related_products')) { add_action($hook, 'woocommerce_output_related_products', 20); } if (function_exists('woocommerce_upsell_display') && !has_action($hook, 'woocommerce_upsell_display')) { add_action($hook, 'woocommerce_upsell_display', 15); } self::convert_aieo_rails($hook); } private static function convert_aieo_rails($hook) { global $wp_filter; if (!isset($wp_filter[$hook])) { return; } $rails = self::aieo_rails(); $swap = array(); foreach ($wp_filter[$hook]->callbacks as $prio => $cbs) { foreach ($cbs as $cb) { $name = self::callback_name($cb['function']); foreach ($rails as $needle => $spec) { if (false !== stripos($name, $needle)) { $swap[] = array($cb['function'], $prio, $spec); break; } } } } foreach ($swap as $s) { list($fn, $prio, $spec) = $s; remove_action($hook, $fn, $prio); add_action($hook, static function () use ($spec) { $pid = get_the_ID(); if (!$pid) { return; } $parent = wp_get_post_parent_id($pid); if ($parent) { $pid = $parent; } $ids = self::rail_ids($spec['surface'], (int) $pid, self::limit()); self::render_rail($spec['id'], $spec['surface'], $spec['heading'], $ids); }, $prio); } } private static function lift_loop_widgets_above_theme_wrapper() { global $wp_filter; $hook = 'woocommerce_after_shop_loop_item'; if (!isset($wp_filter[$hook])) { return; } $wrapper_prio = (int) apply_filters('aieo_theme_loop_footer_priority', 9); $target = max(0, $wrapper_prio - 3); $move = array(); foreach ($wp_filter[$hook]->callbacks as $prio => $cbs) { if ($prio < $wrapper_prio) { continue; } foreach ($cbs as $cb) { $name = self::callback_name($cb['function']); if (false !== stripos($name, 'aieo')) { $move[] = array($cb['function'], $prio); } } } foreach ($move as $m) { remove_action($hook, $m[0], $m[1]); add_action($hook, $m[0], $target); } } public static function feed_our_ids($related, $product_id, $args) { if (!self::is_active()) { return $related; } $ids = self::our_ids((int) $product_id, self::limit()); if (!empty($args['excluded_ids']) && is_array($args['excluded_ids'])) { $ids = array_values(array_diff($ids, array_map('intval', $args['excluded_ids']))); } return empty($ids) ? $related : $ids; } public static function our_ids($product_id, $limit) { return self::our_ids_for_surface($product_id, 'related', $limit); } public static function our_ids_for_surface($product_id, $surface, $limit) { if ($product_id <= 0) { return array(); } if ('related' === $surface && class_exists('AIEO_Semantic_Reco_Source') && AIEO_Semantic_Reco_Source::available()) { $ids = (array) AIEO_Semantic_Reco_Source::ids_for($product_id, $limit); $ids = array_values(array_filter(array_map('intval', $ids))); if (!empty($ids)) { return $ids; } } if (class_exists('AIEO_Effective_Recos') && AIEO_Effective_Recos::is_enabled()) { $audience = class_exists('AIEO_Identity_Bridge') ? AIEO_Identity_Bridge::audience_key_for_current_user() : 'guest'; $ids = (array) AIEO_Effective_Recos::get_top($product_id, $surface, $audience, $limit); $ids = array_values(array_filter(array_map('intval', $ids))); if (!empty($ids)) { return $ids; } } return array(); } public static function related_args($args) { if (!self::is_active()) { return $args; } if (!is_array($args)) { $args = array(); } $args['posts_per_page'] = self::limit(); $args['orderby'] = 'none'; $args['order'] = 'asc'; return $args; } public static function feed_our_upsell_ids($ids, $product) { static $inside = false; if (!self::is_active() || $inside) { return $ids; } if (!is_object($product) || !method_exists($product, 'get_id')) { return $ids; } if (!did_action('woocommerce_after_single_product_summary') && !doing_action('woocommerce_after_single_product_summary')) { return $ids; } $inside = true; $ours = self::our_ids_for_surface((int) $product->get_id(), 'upsells', self::limit()); $inside = false; return empty($ours) ? $ids : $ours; } public static function force_orderby_none($v) { return self::is_active() ? 'none' : $v; } public static function force_order_asc($v) { return self::is_active() ? 'asc' : $v; } public static function force_limit($v) { return self::is_active() ? self::limit() : $v; } public static function keep_our_rank($shuffle) { return self::is_active() ? false : $shuffle; } public static function limit() { return max(1, (int) apply_filters('aieo_theme_native_reco_limit', self::DEFAULT_LIMIT)); } public static function enqueue_widget_scale() { if (!self::is_active()) { return; } $css = '.related.products .aieo-rating-stars--archive{' . '--aieo-rating-size:' . (int) apply_filters('aieo_theme_native_star_size', 14) . 'px;' . '--aieo-rating-cap-fs-arch:10px;' . 'width:100%;display:flex;flex-direction:column;align-items:center;text-align:center}' . '.related.products .aieo-rating-stars--archive .aieo-rating-stars__row{justify-content:center}'; wp_register_style('aieo-theme-native-recos', false, array(), AIEO_VERSION); wp_enqueue_style('aieo-theme-native-recos'); wp_add_inline_style('aieo-theme-native-recos', $css); } public static function invalidate_theme_render_cache($product_id) { $product_id = (int) $product_id; if ($product_id <= 0) { return; } if (class_exists('\ReyCore\Ajax') && defined('\ReyCore\Ajax::AJAX_TRANSIENT_NAME')) { $prefix = constant('\ReyCore\Ajax::AJAX_TRANSIENT_NAME'); delete_transient($prefix . '_related_products_' . $product_id); delete_transient($prefix . '_upsells_products_' . $product_id); } do_action('aieo_theme_native_invalidate', $product_id); } private static function aieo_rails() { return array( 'aieo_show_same_needs_placeholder' => array( 'surface' => 'same_needs', 'id' => 'aieo-same-needs-rail', 'heading' => __('Products for the Same Needs', 'ai-eshop-optimizer'), ), 'aieo_show_cross_sell_placeholder' => array( 'surface' => 'crosssells', 'id' => 'aieo-cross-sells-rail', 'heading' => __('Cross-sell Recommendations', 'ai-eshop-optimizer'), ), 'aieo_show_recently_viewed_placeholder' => array( 'surface' => 'recently_viewed', 'id' => 'aieo-recently-viewed-rail', 'heading' => __('Recently Viewed Products', 'ai-eshop-optimizer'), ), 'AIEO_Semantic_Related_Placement::place_classic' => array( 'surface' => 'semantic_related', 'id' => 'aieo-semantic-related-rail', 'heading' => __('You may also like', 'ai-eshop-optimizer'), ), ); } public static function rail_ids($surface, $product_id, $limit) { $product_id = (int) $product_id; switch ($surface) { case 'semantic_related': if (class_exists('AIEO_Semantic_Reco_Source') && AIEO_Semantic_Reco_Source::available()) { return array_map('intval', (array) AIEO_Semantic_Reco_Source::ids_for($product_id, $limit)); } return array(); case 'recently_viewed': $raw = isset($_COOKIE['woocommerce_recently_viewed']) ? wp_unslash($_COOKIE['woocommerce_recently_viewed']) : ''; $ids = array_filter(array_map('absint', explode('|', (string) $raw))); $ids = array_values(array_diff($ids, array($product_id))); return array_slice($ids, 0, $limit); case 'crosssells': $ours = self::our_ids_for_surface($product_id, 'crosssells', $limit); if (!empty($ours)) { return $ours; } $meta = get_post_meta($product_id, '_crosssell_ids', true); return is_array($meta) ? array_slice(array_map('intval', $meta), 0, $limit) : array(); case 'same_needs': global $wpdb; $table = $wpdb->prefix . 'aieo_product_need_suggestions'; $json = $wpdb->get_var($wpdb->prepare( "SELECT suggested_products FROM {$table} WHERE product_id = %d LIMIT 1", $product_id )); if (!$json) { return array(); } $ids = json_decode($json, true); return is_array($ids) ? array_slice(array_map('intval', $ids), 0, $limit) : array(); } return array(); } public static function render_rail($section_id, $type, $heading, array $ids) { if (empty($ids) || !function_exists('wc_get_product')) { return; } $products = array_filter(array_map('wc_get_product', $ids)); if (function_exists('wc_products_array_filter_visible')) { $products = array_filter($products, 'wc_products_array_filter_visible'); } if (empty($products)) { return; } global $post, $product; $prev_post = $post; $prev_product = $product; $columns = (int) apply_filters('aieo_theme_native_rail_columns', 4, $type); if (function_exists('wc_set_loop_prop')) { wc_set_loop_prop('columns', $columns); wc_set_loop_prop('name', $type); wc_set_loop_prop('is_paginated', false); wc_set_loop_prop('total', count($products)); } $lazy = static function ($attr) { $attr = is_array($attr) ? $attr : array(); $attr['loading'] = 'lazy'; unset($attr['fetchpriority']); $attr['sizes'] = '(max-width: 599px) 50vw, (max-width: 1279px) 25vw, 320px'; return $attr; }; $optim = static function ($attrs) { $attrs = is_array($attrs) ? $attrs : array(); $attrs['loading'] = 'lazy'; unset($attrs['fetchpriority']); return $attrs; }; add_filter('wp_get_attachment_image_attributes', $lazy, 50); add_filter('wp_get_loading_optimization_attributes', $optim, 50); echo ''; remove_filter('wp_get_attachment_image_attributes', $lazy, 50); remove_filter('wp_get_loading_optimization_attributes', $optim, 50); $post = $prev_post; $product = $prev_product; wp_reset_postdata(); } private static function callback_name($fn) { if (is_string($fn)) { return $fn; } if (is_array($fn) && isset($fn[0], $fn[1])) { $c = is_object($fn[0]) ? get_class($fn[0]) : (string) $fn[0]; return $c . '::' . $fn[1]; } return ''; } }