ID) { return $content; } if (!in_array($post->post_type, self::auto_post_types(), true)) { return $content; } if (self::$explicit) { return $content; } $html = self::render(array('objectId' => (int) $post->ID, 'objectType' => $post->post_type)); return ('' === $html) ? $content : ($content . $html); } public static function register_block() { if (!function_exists('register_block_type') || !defined('AIEO_DIR')) { return; } $dir = AIEO_DIR . 'includes/dmm/blocks/semantic-related'; if (!is_dir($dir)) { return; } $title = __('Related content', 'ai-eshop-optimizer'); if (class_exists('AIEO_Blocks_Loader') && method_exists('AIEO_Blocks_Loader', 'aieo_title')) { $title = AIEO_Blocks_Loader::aieo_title($title); } register_block_type($dir, array( 'api_version' => 3, 'title' => $title, 'description' => __('Related articles, guides and products, chosen by the semantic engine.', 'ai-eshop-optimizer'), 'category' => 'widgets', 'icon' => 'admin-links', 'keywords' => array('semantic', 'related', 'recommendations', 'aieo', 'rooster'), 'supports' => array('align' => array('wide', 'full')), 'attributes' => array( 'title' => array('type' => 'string', 'default' => ''), 'limit' => array('type' => 'integer', 'default' => 0), 'objectId' => array('type' => 'integer', 'default' => 0), 'objectType' => array('type' => 'string', 'default' => ''), 'targetTypes' => array('type' => 'string', 'default' => ''), ), 'uses_context' => array('postId'), 'render_callback' => array(__CLASS__, 'render'), )); } public static function shortcode($atts) { self::$explicit = true; $atts = shortcode_atts(array( 'title' => '', 'limit' => 0, 'id' => 0, 'type' => '', 'source_type' => '', 'show' => '', ), $atts, 'aieo_semantic_related'); $source = ('' !== (string) $atts['source_type']) ? (string) $atts['source_type'] : (string) $atts['type']; return self::render(array( 'title' => (string) $atts['title'], 'limit' => (int) $atts['limit'], 'objectId' => (int) $atts['id'], 'objectType' => $source, 'targetTypes' => (string) $atts['show'], )); } public static function render($attrs, $content = '', $block = null) { if (!class_exists('AIEO_Semantic_Reco_Source') || !AIEO_Semantic_Reco_Source::available()) { return ''; } $attrs = wp_parse_args((array) $attrs, array( 'title' => '', 'limit' => 0, 'objectId' => 0, 'objectType' => '', 'targetTypes' => '', )); $object_id = (int) $attrs['objectId']; if ($object_id <= 0 && $block instanceof WP_Block && isset($block->context['postId'])) { $object_id = (int) $block->context['postId']; } if ($object_id <= 0) { $object_id = (int) get_the_ID(); } if ($object_id <= 0) { return ''; } $object_type = (string) $attrs['objectType']; if ('' === $object_type) { $object_type = (string) get_post_type($object_id); } if ('' === $object_type) { return ''; } if ($block instanceof WP_Block) { self::$explicit = true; } $limit = (int) $attrs['limit']; if ($limit <= 0 && class_exists('AIEO_DMM_Settings')) { $limit = (int) AIEO_DMM_Settings::get('semantic.related_block_limit', self::DEFAULT_LIMIT); } $limit = max(1, min(self::MAX_LIMIT, $limit ? $limit : self::DEFAULT_LIMIT)); $target_types = AIEO_Semantic_Reco_Source::sanitize_target_types($attrs['targetTypes']); $rows = AIEO_Semantic_Reco_Source::rows_for($object_type, $object_id, $limit, false, $target_types); if (empty($rows)) { return ''; } $rows = AIEO_Semantic_Reco_Source::diversify_rows($rows, $limit * 2); $cards = self::render_cards($rows, $object_id, $limit); if ('' === $cards) { return ''; } $title = (string) $attrs['title']; if ('' === $title && class_exists('AIEO_DMM_Settings')) { $title = (string) AIEO_DMM_Settings::get('semantic.related_block_title', ''); } if ('' === $title) { $title = __('You may also like', 'ai-eshop-optimizer'); } self::enqueue_styles(); $wrap = ($block instanceof WP_Block) ? get_block_wrapper_attributes(array('class' => 'aieo-recommendations aieo-semantic-related')) : 'class="aieo-recommendations aieo-semantic-related"'; $out = '
'; $out .= '

' . esc_html($title) . '

'; $out .= '
' . $cards . '
'; $out .= '
'; return $out; } private static function render_cards(array $rows, $object_id, $limit) { $out = ''; $done = 0; foreach ($rows as $row) { if ($done >= $limit) { break; } $target_id = isset($row['target_id']) ? (int) $row['target_id'] : 0; $target_type = isset($row['target_type']) ? (string) $row['target_type'] : ''; if ($target_id <= 0 || $target_id === (int) $object_id) { continue; } $card = ('product' === $target_type && function_exists('wc_get_product')) ? self::product_card($target_id, $row) : self::post_card($target_id, $target_type, $row); if ('' === $card) { continue; } $out .= $card; $done++; } return $out; } private static function provenance_attrs(array $row) { $attrs = ' data-aieo-rank="' . esc_attr((string) (isset($row['rank']) ? (int) $row['rank'] : 0)) . '"'; $reason = isset($row['reason']) ? (string) $row['reason'] : ''; if ('' !== $reason) { $attrs .= ' data-aieo-reason="' . esc_attr($reason) . '"'; } $type = isset($row['target_type']) ? (string) $row['target_type'] : ''; if ('' !== $type) { $attrs .= ' data-aieo-target-type="' . esc_attr($type) . '"'; } return $attrs; } public static function image_size() { $size = (function_exists('wc_get_image_size') && has_image_size('woocommerce_thumbnail')) ? 'woocommerce_thumbnail' : 'medium_large'; return (string) apply_filters('aieo_semantic_related_image_size', $size); } private static function product_card($id, array $row) { $product = wc_get_product($id); if (!$product || !is_a($product, 'WC_Product') || !$product->is_visible()) { return ''; } $url = get_permalink($id); if (!is_string($url) || '' === $url) { return ''; } $classes = 'aieo-card'; if ($product->is_on_sale()) { $classes .= ' is-on-sale'; } if (!$product->is_in_stock()) { $classes .= ' is-out-of-stock'; } $out = ''; $out .= '
' . $product->get_image(self::image_size(), array('loading' => 'lazy')) . '
'; $out .= '
'; $out .= '

' . esc_html($product->get_name()) . '

'; $out .= '
' . preg_replace('#]*>#i', '', $product->get_price_html()) . '
'; $out .= '
'; return $out; } private static function post_card($id, $type, array $row) { $post = get_post($id); if (!$post instanceof WP_Post || 'publish' !== $post->post_status) { return ''; } if ('' !== $type && $post->post_type !== $type) { return ''; } $obj = get_post_type_object($post->post_type); if (!$obj || empty($obj->public)) { return ''; } $url = get_permalink($post); if (!is_string($url) || '' === $url) { return ''; } $out = ''; $thumb = get_the_post_thumbnail($post, self::image_size(), array('loading' => 'lazy')); $out .= (is_string($thumb) && '' !== $thumb) ? '
' . $thumb . '
' : ''; $out .= '
'; $out .= '

' . esc_html(get_the_title($post)) . '

'; $out .= '
' . esc_html(self::card_meta($post)) . '
'; $out .= '
'; return $out; } public static function card_meta($post) { $meta = ''; if ('post' === $post->post_type) { $meta = (string) get_the_date('', $post); } else { $obj = get_post_type_object($post->post_type); if ($obj && isset($obj->labels->singular_name)) { $meta = (string) $obj->labels->singular_name; } } return (string) apply_filters('aieo_semantic_related_card_meta', $meta, $post); } private static function enqueue_styles() { static $done = false; if ($done) { return; } $done = true; if (wp_style_is('aieo-recommendations', 'registered') || wp_style_is('aieo-recommendations', 'enqueued')) { wp_enqueue_style('aieo-recommendations'); return; } $css = '.aieo-semantic-related{margin:32px 0}' . '.aieo-semantic-related .aieo-recommendations-title{font-size:14px;letter-spacing:.08em;' . 'text-transform:uppercase;border-bottom:1px solid #e5e7eb;padding-bottom:8px;margin:0 0 16px}' . '.aieo-semantic-related .aieo-recommendations-grid{display:grid;gap:16px;' . 'grid-template-columns:repeat(auto-fill,minmax(160px,1fr))}' . '.aieo-semantic-related .aieo-card{display:block;text-decoration:none;color:inherit}' . '.aieo-semantic-related .aieo-card img{width:100%;height:auto;display:block}' . '.aieo-semantic-related .aieo-card-img-wrap--empty{aspect-ratio:1/1;' . 'background:linear-gradient(135deg,#f3f4f6,#e5e7eb)}' . '.aieo-semantic-related .aieo-card-name{font-size:14px;margin:8px 0 4px;font-weight:500}' . '.aieo-semantic-related .aieo-card-price{font-size:13px;color:#374151}' . '.aieo-semantic-related .aieo-card-meta{font-size:12px;color:#6b7280;min-height:2.5em}'; wp_register_style('aieo-semantic-related', false, array(), null); wp_add_inline_style('aieo-semantic-related', $css); wp_enqueue_style('aieo-semantic-related'); } }