' . aksrt_render_graphic_card_post($post) . '
';
} else {
$subtitle = class_exists('\AKSRT\Core\PostMetaBox')
? (string) \AKSRT\Core\PostMetaBox::get_meta_value($post->ID, 'subtitle', '')
@@ -692,7 +534,7 @@ if (!function_exists('aksrt_single_render_related')) :
+
';
return (string) ob_get_clean();
}
endif;
diff --git a/inc/helpers/template-tags/search-meta.php b/inc/helpers/template-tags/search-meta.php
index 87f40049..b3f3c996 100644
--- a/inc/helpers/template-tags/search-meta.php
+++ b/inc/helpers/template-tags/search-meta.php
@@ -470,22 +470,52 @@ endif;
if (!function_exists('aksrt_time_ago')) :
/**
- * 将日期转为相对时间描述(中文)
+ * 将日期转为相对时间描述(中文,legacy zib_get_time_ago parity)。
*
* @param string $date 日期字符串(MySQL datetime 格式)
* @return string 例如 "19天前"、"3个月前"
*/
function aksrt_time_ago(string $date): string {
- $diff = time() - strtotime($date);
+ $time = (int) strtotime($date);
+ $ctime = (int) strtotime(current_time('mysql'));
+ $t = $ctime - $time;
- if ($diff < 0) return __('刚刚', 'aksrt');
- if ($diff < 60) return __('刚刚', 'aksrt');
- if ($diff < 3600) return sprintf(__('%d分钟前', 'aksrt'), (int) floor($diff / 60));
- if ($diff < 86400) return sprintf(__('%d小时前', 'aksrt'), (int) floor($diff / 3600));
- if ($diff < 2592000) return sprintf(__('%d天前', 'aksrt'), (int) floor($diff / 86400));
- if ($diff < 31536000) return sprintf(__('%d个月前', 'aksrt'), (int) floor($diff / 2592000));
-
- return sprintf(__('%d年前', 'aksrt'), (int) floor($diff / 31536000));
+ if ($t < 0) {
+ return date('Y-m-d H:i', $time);
+ }
+ $y = (int) date('Y', $ctime) - (int) date('Y', $time);
+ if (0 === $t) {
+ return __('刚刚', 'aksrt');
+ }
+ if ($t < 60) {
+ return sprintf(__('%d秒前', 'aksrt'), $t);
+ }
+ if ($t < 3600) {
+ return sprintf(__('%d分钟前', 'aksrt'), (int) floor($t / 60));
+ }
+ if ($t < 86400) {
+ return sprintf(__('%d小时前', 'aksrt'), (int) floor($t / 3600));
+ }
+ if ($t < 2592000) {
+ if ($time > (int) strtotime(date('Ymd', (int) strtotime('-1 day')))) {
+ return __('昨天', 'aksrt');
+ }
+ if ($time > (int) strtotime(date('Ymd', (int) strtotime('-2 days')))) {
+ return __('前天', 'aksrt');
+ }
+ return sprintf(__('%d天前', 'aksrt'), (int) floor($t / 86400));
+ }
+ if ($t < 31536000 && 0 === $y) {
+ $m = (int) date('m', $ctime) - (int) date('m', $time) - 1;
+ if (0 === $m) {
+ return sprintf(__('%d天前', 'aksrt'), (int) floor($t / 86400));
+ }
+ return sprintf(__('%d个月前', 'aksrt'), $m);
+ }
+ if ($t < 31536000 && $y > 0) {
+ return sprintf(__('%d个月前', 'aksrt'), 12 - (int) date('m', $time) + (int) date('m', $ctime));
+ }
+ return sprintf(__('%d年前', 'aksrt'), (int) date('Y', $ctime) - (int) date('Y', $time));
}
endif;
diff --git a/src/WidgetSystem/Renderers/UserRenderer.php b/src/WidgetSystem/Renderers/UserRenderer.php
index 44d47b3b..5a438569 100644
--- a/src/WidgetSystem/Renderers/UserRenderer.php
+++ b/src/WidgetSystem/Renderers/UserRenderer.php
@@ -106,7 +106,7 @@ final class UserRenderer {
}
$html .= '
';
if (!empty($i['show_posts']) && function_exists('aksrt_render_user_card_posts')) {
- $html .= '
' . aksrt_render_user_card_posts(['user_id' => $user_id, 'count' => 4, 'post_style' => 'mini', 'show_empty' => false]) . '
';
+ $html .= '
' . aksrt_render_user_card_posts(['user_id' => $user_id, 'limit' => 4, 'post_style' => 'mini']) . '
';
}
return $html . '';
}
diff --git a/templates/parts/resource-related.php b/templates/parts/resource-related.php
index d11d5d4c..64fbf54d 100644
--- a/templates/parts/resource-related.php
+++ b/templates/parts/resource-related.php
@@ -51,7 +51,9 @@ if (!$query->have_posts()) {
$query->the_post();
$post = get_post();
if ($post instanceof WP_Post) {
- echo aksrt_render_related_graphic_card($post); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ echo '
' .
+ aksrt_render_graphic_card_post($post) . // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ '
';
}
}
wp_reset_postdata();
diff --git a/tests/widget-system/post-visual-contract.test.cjs b/tests/widget-system/post-visual-contract.test.cjs
index be153935..036e1f53 100644
--- a/tests/widget-system/post-visual-contract.test.cjs
+++ b/tests/widget-system/post-visual-contract.test.cjs
@@ -82,7 +82,8 @@ test('settings use legacy list modes, grouped options, and destructive related k
for (const key of legacyKeys) {
assert.equal(source.includes(key), false, `legacy article key remains: ${key}`);
}
- assert.doesNotMatch(source, /list_card_topics|is_topics|\btopics\b/);
+ // bare `topics` is legal again: the related-posts query mirrors the legacy taxonomy OR.
+ assert.doesNotMatch(source, /list_card_topics|is_topics/);
});
test('forced mode wins and thumb_if_has ignores spare and lazy placeholders', () => {
@@ -226,12 +227,18 @@ test('related posts, author page and widgets use their dedicated renderers', ()
assert.match(related, /\['img', 'list', 'text'\]/);
assert.match(related, /aksrt_renderer_mini\(\$data, \$mini_cfg\)/);
- assert.match(related, /aksrt_render_related_graphic_card\(\$post\)/);
+ assert.match(related, /\
' \. aksrt_render_graphic_card_post\(\$post\)/);
assert.match(related, /
\s+array_map\('intval', \(array\) \$term_ids\)/);
+ assert.match(related, /class=\"zib-widget\"/);
+ assert.doesNotMatch(related, /post_related_source/);
+ assert.doesNotMatch(related, /post__not_in/);
+ assert.match(related, /\$args\['meta_key'\]\s*=\s*'aksrt_post_views'/);
assert.match(relatedCard, /aksrt_renderer_resolve_images/);
+ assert.match(relatedCard, /'text2'\s+=>\s+esc_html\(aksrt_str_cut\(\$title, 0, \(int\) \$args\['cut'\], '\.\.\.'\)\)/);
assert.match(resourceRelated, /swiper-container swiper-scroll/);
- assert.match(resourceRelated, /aksrt_render_related_graphic_card/);
+ assert.match(resourceRelated, /aksrt_render_graphic_card_post/);
assert.doesNotMatch(resourceRelated, /aksrt_render_post_list/);
assert.match(author, /aksrt_render_post_list/);
assert.doesNotMatch(author, /'type'\s*=>\s*'list'/);
@@ -245,6 +252,36 @@ test('related posts, author page and widgets use their dedicated renderers', ()
assert.match(widgets, /swiper-container swiper-scroll/);
});
+test('after-box author card mirrors legacy user-card structure', () => {
+ const render = read('inc/helpers/aksrt-single-render.php');
+ const identity = read('inc/helpers/aksrt-identity.php');
+ const avatar = read('inc/helpers/aksrt-avatar.php');
+ const userCard = read('inc/helpers/aksrt-user-card.php');
+ const css = read('assets/css/frontend/single-post/related-toc.css');
+ const authorActions = read('assets/css/frontend/single-post/author-actions.css');
+
+ assert.match(render, /avatar-img avatar-lg/);
+ assert.match(render, /'vip'\s+=>\s+false/);
+ assert.match(render, /'follow'\s+=>\s+true/);
+ assert.match(render, /