code; } public function get_error_message(): string { return $this->message; } } class WP_Post { public int $ID; public function __construct(int $id) { $this->ID = $id; } } class WP_Term { public int $term_id; public string $name = ''; public string $description = ''; public string $taxonomy = 'category'; public int $term_taxonomy_id; public function __construct(int $term_id, string $name, string $description = '', string $taxonomy = 'category') { $this->term_id = $term_id; $this->name = $name; $this->description = $description; $this->taxonomy = $taxonomy; $this->term_taxonomy_id = $term_id; } } class WP_User { public int $ID; public string $display_name; public function __construct(int $id, string $name) { $this->ID = $id; $this->display_name = $name; } } class wpdb { public string $postmeta = 'wp_postmeta'; public string $posts = 'wp_posts'; public string $term_relationships = 'wp_term_relationships'; public function prepare(string $query, ...$args): string { foreach ($args as $arg) { $query = preg_replace('/%[sd]/', is_numeric($arg) ? (string) (int) $arg : "'" . (string) $arg . "'", $query, 1); } return $query; } public function get_var(string $query): ?string { unset($query); return '12345'; } } $GLOBALS['wpdb'] = new wpdb(); // Fake posts the stub WP_Query will serve, keyed by expected cat. $GLOBALS['aksrt_harness_posts'] = [ ['id' => 101, 'title' => '第一篇文章', 'cat' => 5], ['id' => 102, 'title' => '第二篇文章', 'cat' => 5], ['id' => 103, 'title' => '第三篇文章', 'cat' => 5], ['id' => 104, 'title' => '第四篇文章', 'cat' => 5], ]; $GLOBALS['aksrt_harness_cursor'] = 0; class WP_Query { public array $posts = []; public int $found_posts = 0; public function __construct(public array $query_args = []) { $cat_in = (array) ($query_args['category__in'] ?? []); $matched = array_values(array_filter($GLOBALS['aksrt_harness_posts'], static fn(array $p): bool => in_array($p['cat'], $cat_in, true))); $this->found_posts = count($matched); foreach ($matched as $post) { $this->posts[] = new WP_Post($post['id']); $GLOBALS['aksrt_harness_lookup'][$post['id']] = $post; } } public function have_posts(): bool { return $GLOBALS['aksrt_harness_cursor'] < count($this->posts); } public function the_post(): void { $post = $this->posts[$GLOBALS['aksrt_harness_cursor']]; $GLOBALS['aksrt_harness_cursor']++; $GLOBALS['aksrt_harness_current_post'] = $GLOBALS['aksrt_harness_lookup'][$post->ID] ?? ['id' => $post->ID, 'title' => '文章' . $post->ID]; } } function __($text, $domain = null): string { unset($domain); return (string) $text; } function esc_html__($text, $domain = null): string { return esc_html(__($text, $domain)); } function esc_attr__($text, $domain = null): string { return esc_attr(__($text, $domain)); } function esc_html($value): string { return htmlspecialchars((string) $value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); } function esc_attr($value): string { return esc_html($value); } function esc_url_raw($value): string { $value = trim((string) $value); return preg_match('#^(https?://|/)#', $value) ? $value : ''; } function esc_url($value): string { return esc_attr(esc_url_raw($value)); } function absint($value): int { return abs((int) $value); } function sanitize_key($value): string { return preg_replace('/[^a-z0-9_\-]/', '', strtolower((string) $value)) ?? ''; } function sanitize_text_field($value): string { return trim(strip_tags((string) $value)); } function sanitize_textarea_field($value): string { return trim(strip_tags((string) $value)); } function sanitize_html_class($value): string { return preg_replace('/[^A-Za-z0-9_-]/', '', (string) $value) ?? ''; } function wp_parse_args($args, array $defaults = []): array { return array_merge($defaults, is_array($args) ? $args : []); } function wp_kses($value, array $allowed): string { unset($allowed); return strip_tags((string) $value, '
'); } function wp_kses_post($value): string { return strip_tags((string) $value, '

'); } function wp_strip_all_tags($value): string { return trim(strip_tags((string) $value)); } function wp_trim_words($text, int $count = 55, string $more = '…'): string { $words = preg_split('/\s+/', trim((string) $text)) ?: []; if (count($words) <= $count) return implode(' ', $words); return implode(' ', array_slice($words, 0, $count)) . $more; } function wp_is_mobile(): bool { return false; } function wp_reset_postdata(): void {} function is_wp_error($value): bool { return $value instanceof WP_Error; } function is_super_admin(): bool { return false; } function is_sticky($id = 0): bool { unset($id); return false; } function current_time(string $type): int { return $type === 'timestamp' ? time() : 0; } function human_time_diff(int $from, int $to = 0): string { unset($to); return (($from % 3600) + 1) . ' 小时'; } function get_avatar_url($id, array $args = []): string { return 'https://example.test/avatar-' . (int) $id . '.jpg'; } function get_post_meta($id, $key = '', $single = false) { unset($id, $key, $single); return ''; } function get_term_meta(int $term_id, string $key, bool $single = false) { if ($key === 'aksrt_category_cover_id') { return $term_id === 5 ? 55 : 0; } return ''; } function wp_get_attachment_url(int $id): string { return $id === 55 ? 'https://example.test/cover-cat-5.jpg' : ''; } function get_posts(array $args): array { unset($args); return [102]; } function get_the_post_thumbnail_url($post = null, string $size = 'post-thumbnail') { unset($post, $size); return 'https://example.test/thumb-cat-8.jpg'; } function get_terms(array $args): array { unset($args); return [new WP_Term(5, '技术分享', '这里是分类描述文本'), new WP_Term(8, '生活记录', '')]; } function get_term(int $term_id, string $taxonomy = '') { unset($taxonomy); return match ($term_id) { 5 => new WP_Term(5, '技术分享', '这里是分类描述文本'), 8 => new WP_Term(8, '生活记录', ''), default => new WP_Error('invalid_term', 'Term not found'), }; } function get_term_link(WP_Term $term) { return 'https://example.test/cat-' . $term->term_id; } function get_the_ID(): int { return (int) ($GLOBALS['aksrt_harness_current_post']['id'] ?? 0); } function get_the_title($id = 0): string { unset($id); return (string) ($GLOBALS['aksrt_harness_current_post']['title'] ?? ''); } function get_permalink($id = 0): string { return 'https://example.test/post-' . (int) ($id ?: get_the_ID()); } function get_the_excerpt($id = 0): string { unset($id); return '这是摘要'; } function get_post_field($field, $id = 0, $context = 'display') { unset($context); return $field === 'post_author' ? 1 : ''; } function get_userdata($id): ?WP_User { return $id > 0 ? new WP_User((int) $id, '作者' . (int) $id) : null; } function get_the_time($format, $id = 0): string { unset($format, $id); return '2026-09-01 10:00:00'; } function get_the_modified_time($format, $id = 0): string { unset($format, $id); return '2026-09-04 10:00:00'; } function get_the_category($id = 0): array { unset($id); return [new WP_Term(5, '技术分享')]; } function get_the_tags($id = 0) { unset($id); return []; } function get_comments_number($id = 0): int { unset($id); return 3; } function get_comments_link($id = 0): string { return get_permalink($id) . '#comments'; } function comments_open($id = 0): bool { unset($id); return true; } function get_post_type($id = 0): string { unset($id); return 'post'; } function has_post_format($formats, $id = 0): bool { unset($formats, $id); return false; } function aksrt_get_pay_meta($post = null): array { unset($post); return ['pay_type' => 'no', 'price' => 0]; } function aksrt_get_post_views($id = 0): int { return (int) ($id % 90) + 10; } function aksrt_get_post_likes($id = 0): int { return (int) ($id % 7); } function aksrt_get_post_favorites($id = 0): int { return (int) ($id % 5); } function aksrt_resource_card_badge_text($id = 0): string { unset($id); return ''; } function aksrt_format_number($num): string { return (string) $num; } function aksrt_is_lazy(string $key, bool $default = false): bool { unset($key); return $default; } function aksrt_get_lazy_thumb(string $size = ''): string { unset($size); return 'https://example.test/lazy.svg'; } function aksrt_get_lazy_attr(string $type, string $src, string $class = '', string $lazy_src = ''): string { $url = $lazy_src !== '' ? $lazy_src : $src; return ' class="' . esc_attr($class) . '" src="' . esc_url($url) . '" data-src="' . esc_url($src) . '" data-lazy-type="' . esc_attr($type) . '"'; } function aksrt_renderer_resolve_images($post_id, array $cfg): array { unset($post_id, $cfg); return ['primary' => '', 'has_real' => false, 'gallery' => [], 'slides' => [], 'is_slide' => false, 'video_url' => '']; } // --- full pipeline stubs (AssetManager / SchemaWidget / registry) --- function wp_register_style(...$args): void { unset($args); } function wp_register_script(...$args): void { unset($args); } function wp_enqueue_style(...$args): void { unset($args); } function wp_enqueue_script(...$args): void { unset($args); } function wp_add_inline_script(...$args): void { unset($args); } function rest_url(string $path = ''): string { return 'https://example.test/wp-json/' . $path; } function get_template_directory(): string { return dirname(__DIR__, 2); } function get_template_directory_uri(): string { return 'https://example.test/theme'; } function wp_json_encode($value): string { return (string) json_encode($value, JSON_UNESCAPED_UNICODE); } function esc_textarea($value): string { return esc_html($value); } function checked($actual, $expected = true, bool $display = true): string { $result = $actual == $expected ? 'checked="checked"' : ''; if ($display) echo $result; return $result; } function selected($actual, $expected = true, bool $display = true): string { $result = $actual == $expected ? 'selected="selected"' : ''; if ($display) echo $result; return $result; } class WP_Widget { public string $id_base = ''; public string $id = ''; public int $number = 0; public function __construct(string $id_base = '') { $this->id_base = $id_base; } public function get_field_name(string $field): string { return 'widget-' . $this->id_base . '[' . $this->number . '][' . $field . ']'; } public function get_field_id(string $field): string { return 'widget-' . $this->id_base . '-' . $this->number . '-' . $field; } public function _set(int $number): void { $this->number = $number; $this->id = $this->id_base . '-' . $number; } } require dirname(__DIR__, 2) . '/vendor/autoload.php'; require dirname(__DIR__, 2) . '/inc/helpers/aksrt-graphic-card.php'; require dirname(__DIR__, 2) . '/inc/helpers/aksrt-widgets-markup.php'; require dirname(__DIR__, 2) . '/inc/helpers/posts-renderer/post-data.php'; use AKSRT\WidgetSystem\Renderers\ContentRenderer; $instance = [ 'term_id' => [5, 8], 'pc_row' => 2, 'orderby' => 'modified', 'order' => 'desc', 'count' => 4, 'target_blank' => false, 'obs_animation' => '', 'animation_repeat' => false, ]; try { $html = ContentRenderer::render('term_aggregation', $instance); echo "=== RENDER OK ===\n"; echo $html; echo "\n=== END ===\n"; } catch (Throwable $e) { echo '=== FATAL: ' . get_class($e) . ': ' . $e->getMessage() . ' @ ' . $e->getFile() . ':' . $e->getLine() . "\n"; echo $e->getTraceAsString() . "\n"; } // Single-term variant (is_row=false) + animation variant. try { $GLOBALS['aksrt_harness_cursor'] = 0; $single = ContentRenderer::render('term_aggregation', array_merge($instance, ['term_id' => [5], 'obs_animation' => 'fade'])); echo "=== SINGLE OK (len=" . strlen($single) . ") ===\n"; echo $single . "\n"; } catch (Throwable $e) { echo '=== SINGLE FATAL: ' . get_class($e) . ': ' . $e->getMessage() . ' @ ' . $e->getFile() . ':' . $e->getLine() . "\n"; } // Full pipeline: registry -> definition -> normalize(raw form input '5,8') -> FrontendRenderer. try { $definitions = \AKSRT\WidgetSystem\WidgetRegistry::all(); echo "=== REGISTRY: term_aggregation present = " . (isset($definitions['term_aggregation']) ? 'yes' : 'NO') . " ===\n"; $definition = $definitions['term_aggregation']; $widget = new \AKSRT\WidgetSystem\SchemaWidget($definition); $widget->_set(2); $normalized = $definition->normalize(['term_id' => '5,8']); echo "=== NORMALIZED term_id = " . json_encode($normalized['term_id']) . " ===\n"; $GLOBALS['aksrt_harness_cursor'] = 0; ob_start(); \AKSRT\WidgetSystem\FrontendRenderer::render( $definition, ['before_widget' => '

', 'after_widget' => '
'], $normalized, $widget ); $full = (string) ob_get_clean(); echo "=== FULL PATH OK (len=" . strlen($full) . ") ===\n"; echo $full . "\n"; // Empty selection must render nothing at all. ob_start(); \AKSRT\WidgetSystem\FrontendRenderer::render($definition, ['before_widget' => '
', 'after_widget' => '
'], $definition->normalize([]), $widget); $empty = (string) ob_get_clean(); echo "=== EMPTY SELECTION OUTPUT len=" . strlen($empty) . " ===\n"; // Admin form HTML for the terms field (what the widgets screen renders). ob_start(); $widget->render_full_form($definition->normalize(['term_id' => '5,8'])); $form = (string) ob_get_clean(); if (preg_match('/
\s*<\/div>\s*<\/div>/s', $form, $m)) { echo "=== TERMS FIELD HTML ===\n" . $m[0] . "\n"; } echo "=== FORM contains hidden terms value name = " . (strpos($form, 'name="widget-aksrt_widget_term_aggregation[2][term_id]"') !== false ? 'yes' : 'NO') . " ===\n"; echo "=== FORM hidden value = "; preg_match('/class="aksrt-widget-terms__value"[^>]*value="([^"]*)"/', $form, $v); echo ($v[1] ?? '(not found)') . " ===\n"; } catch (Throwable $e) { echo '=== FULL PATH FATAL: ' . get_class($e) . ': ' . $e->getMessage() . ' @ ' . $e->getFile() . ':' . $e->getLine() . "\n"; echo $e->getTraceAsString() . "\n"; }