根因不是排序,是候选池:旧 SQL 先圈「最近 30 天发布」再按评论排序取 6。
本站实测 30 天只有 1 篇 post(7 天 0 / 90 天 1 / 180 天 6 / 365 天 15 / 全站 967),
候选池只有 1 条,LIMIT 6 也只能返回 1 条。
改成全站累计热度,三项取对数加权:
3*log10(views+1) + 5*log10(love+1) + 4*log10(comments+1)
· 取 log10:views 中位 4981/最大 56075,而 love 最大 40、评论最大 93,
不压缩则 views 一项吃掉全部权重。
· 权重 3/5/4(不是通用 1/3/5):用通用值实测前 8 名 6 篇是 2009 年老随笔
—— 评论 1.97*5=9.86 > views 4.75*1=4.75,退化成「评论数排行榜」。
· 有意不加时间衰减:HN/Reddit 那套是「新内容榜」,会把老文压到看不见,
正好回到原问题。要时效榜是另一个功能。
· 仍限定 post_type='post'(用户要求只看文章,不看 page;
ID 217「留言本」/522「博客时间」核查过都是 page,天然被挡掉)。
顺手修掉三处旧问题:
· 删 date_default_timezone_set("PRC") —— 它会改掉整个 PHP 请求的全局时区
· 标题进属性改 esc_attr()(旧代码裸拼,含双引号会截断 title 属性)
· get_posts 参数从 'numberposts=6 & offset=0' 字符串改成数组
(字符串里那个空格让参数名变成 ' offset',offset=0 实际从未生效)
后台:「统计天数」输入框下线(它控制的就是那个缩死候选池的时间窗),
update() 不再保存 days,老实例残留值下次保存时丢掉。
验证:
· 主测试 33/0(分数用独立实现重算比对,不调用产品函数算期望值)
· 反向验证 20/0(基线 2 + 框架自检 2 + 注入 8 组×2)
八组注入:时间窗加回 / 去 post_type / 去 log10 / 权重改 1-3-5 /
标题退裸拼 / 全局时区加回 / 排序改升序 / 后台加回统计天数
· 真浏览器几何 22/0(1440+1180 两视口):6 条真画出来、宽度等于面板、
图标存在、不重叠、长标题走省略号不溢出
· 发布反证 8/8、线上包校验 10/10
548 lines
25 KiB
PHP
548 lines
25 KiB
PHP
<?php
|
||
/**
|
||
* 侧栏小工具
|
||
* @author Yongquan Yang <yangyq.net>
|
||
* @license MIT License
|
||
* @version 2020.08.03
|
||
*/
|
||
|
||
// 添加小工具
|
||
function widgets_init()
|
||
{
|
||
register_sidebar(array(
|
||
'name' => __('侧边栏工具', 'kratos'),
|
||
'id' => 'sidebar_tool',
|
||
'before_widget' => '<div class="widget %2$s">',
|
||
'after_widget' => '</div>',
|
||
'before_title' => '<div class="title">',
|
||
'after_title' => '</div>',
|
||
));
|
||
// 去掉默认小工具
|
||
$wp_widget = array(
|
||
'WP_Widget_Pages',
|
||
'WP_Widget_Meta',
|
||
'WP_Widget_Recent_Posts',
|
||
'WP_Widget_Recent_Comments',
|
||
'WP_Widget_RSS',
|
||
'WP_Widget_Search',
|
||
'WP_Widget_Tag_Cloud',
|
||
'WP_Nav_Menu_Widget',
|
||
);
|
||
|
||
foreach ($wp_widget as $wp_widget) {
|
||
unregister_widget($wp_widget);
|
||
}
|
||
}
|
||
add_action('widgets_init', 'widgets_init');
|
||
|
||
/* ==========================================================================
|
||
* 热度算法(3.2.49)
|
||
*
|
||
* ── 为什么废弃旧的 most_comm_posts() ──────────────────────────────────
|
||
* 旧实现的候选池是「最近 N 天(默认 30)**发布**的文章」:
|
||
*
|
||
* WHERE post_date BETWEEN '$daysago' AND '$today'
|
||
* ORDER BY comment_count DESC LIMIT 6
|
||
*
|
||
* 对日更站点没问题,对低频更新的站点直接失效。本站实测(2026-10-26):
|
||
* 7 天内发布 0 篇 / 30 天内 1 篇 / 90 天内 1 篇 / 180 天内 6 篇
|
||
* 30 天候选池只有 1 篇 ⇒ LIMIT 6 也只能返回 1 条。
|
||
* 这就是侧栏「热点」永远只显示一条的真正原因 —— **不是排序错,是池子空**。
|
||
*
|
||
* 而且「最近 30 天发布」本身就不是「热」的定义:热是累计互动,不是新鲜度。
|
||
*
|
||
* ── 现在的算法 ────────────────────────────────────────────────────────
|
||
* 候选池:全站所有已发布 post(不含 page —— 「留言本」这种页面 views 214 万,
|
||
* 它的热度对"最热文章"没有意义;`post_type='post'` 天然挡掉)。
|
||
* 打分:三个指标各取 log10 后加权求和
|
||
*
|
||
* score = 3*log10(views+1) + 5*log10(love+1) + 4*log10(comments+1)
|
||
*
|
||
* 为什么取 log10:本站 views 中位数 4981、最大 56075(差 11 倍),
|
||
* 但 love 最大只有 40、评论最大 93。不取对数的话 views 一个指标就吃掉
|
||
* 全部权重,点赞和评论等于没参与。取对数后三者量级被拉平到可比的区间。
|
||
*
|
||
* 为什么权重是 3 / 5 / 4(不是常见的 1 / 3 / 5):
|
||
* 用通用值实测,前 8 名有 6 篇是 2009 年的老随笔 —— 因为那批文章评论
|
||
* 80~93 条,仅评论一项(1.97×5=9.86)就压过 views 最大值的贡献(4.75×1=4.75),
|
||
* 结果变成"评论数排行榜"。把 views 权重提到 3 之后三个指标影响力均衡:
|
||
* 点赞最高(5)—— 点👍 是比评论更强的认可信号,且本站只有 197 篇有点赞,
|
||
* 稀缺性让它更有区分度;评论次之(4)—— 要付出打字成本;浏览最轻(3)。
|
||
*
|
||
* 实测这组权重的排名(前 8):Anaconda多用户安装(love=40) · kratos主题发布(love=9,评论37)
|
||
* · LaTex上划线(love=19) · 那一年2009 · LaTex字体(love=12) · Windows7自带WMP(评论93)
|
||
* → 技术文章与高互动随笔混合出现,2009 年那批也没有被完全埋掉。
|
||
*
|
||
* ── 有意不加时间衰减 ──────────────────────────────────────────────────
|
||
* 「全站历史最热」是累计榜,不是时效榜。Hacker News / Reddit 那套
|
||
* gravity 衰减是给「新内容榜」用的,加上去会把老文章压到看不见,
|
||
* 那就又回到"只有最近几条"的老问题。要时效榜是另一个功能,不塞在这里。
|
||
*
|
||
* ── 顺手修掉的两个坑 ──────────────────────────────────────────────────
|
||
* ① 旧代码里 `date_default_timezone_set("PRC")` 直接改 **PHP 全局时区**,
|
||
* 影响同一请求里之后所有时间函数。已删。
|
||
* ② 旧代码把 `$title` 原样插进 `title="..."` 属性,标题里有双引号就会
|
||
* 截断属性(甚至注入)。已改用 esc_attr()。
|
||
* ====================================================================== */
|
||
|
||
if (!function_exists('yang_hot_score')):
|
||
|
||
/**
|
||
* 单篇文章的热度分。
|
||
*
|
||
* @param int $views 浏览数
|
||
* @param int $love 点赞数
|
||
* @param int $comments 评论数
|
||
* @return float
|
||
*/
|
||
function yang_hot_score($views, $love, $comments)
|
||
{
|
||
return 3 * log10((int) $views + 1)
|
||
+ 5 * log10((int) $love + 1)
|
||
+ 4 * log10((int) $comments + 1);
|
||
}
|
||
|
||
endif;
|
||
|
||
if (!function_exists('yang_hot_posts')):
|
||
|
||
/**
|
||
* 取热度最高的文章。
|
||
*
|
||
* 为什么在 PHP 里算分、不在 SQL 里 ORDER BY:
|
||
* 分值是三项 log 加权和,SQL 里要写三段 LOG10 + COALESCE + 两个
|
||
* LEFT JOIN 到 postmeta,可读性极差,而且没法复用 yang_hot_score()
|
||
* (测试和产品必须是同一份实现,不能各写一遍)。
|
||
* 代价是要把候选行取回来 —— 本站 967 篇 post,一次取 4 个字段
|
||
* 内存占用可忽略;如果将来文章上万,再加 LIMIT / 缓存。
|
||
* 为防止极端情况,SQL 侧先用 views 粗排取前 N*10(对数排序里
|
||
* 真正的高分不会来自 views 极低的文章,这个粗排是安全的)。
|
||
*
|
||
* @param int $nums 返回条数
|
||
* @return array 文章对象数组,每项含 ID/post_title/views/love/comments/score
|
||
*/
|
||
function yang_hot_posts($nums = 6)
|
||
{
|
||
global $wpdb;
|
||
|
||
$nums = max(1, (int) $nums);
|
||
// 粗排窗口:给足余量。views 前 200 名以外不可能靠 love/评论翻盘
|
||
// (log10 域下,love 满分 1.61×5=8.05,补不回 views 差 1 个数量级的 3 分)。
|
||
$pool = max(200, $nums * 20);
|
||
|
||
$sql = $wpdb->prepare(
|
||
"SELECT p.ID, p.post_title, p.post_date, p.comment_count,
|
||
COALESCE(CAST(mv.meta_value AS UNSIGNED), 0) AS views,
|
||
COALESCE(CAST(ml.meta_value AS UNSIGNED), 0) AS love
|
||
FROM {$wpdb->posts} p
|
||
LEFT JOIN {$wpdb->postmeta} mv ON mv.post_id = p.ID AND mv.meta_key = 'views'
|
||
LEFT JOIN {$wpdb->postmeta} ml ON ml.post_id = p.ID AND ml.meta_key = 'love'
|
||
WHERE p.post_status = 'publish'
|
||
AND p.post_type = 'post'
|
||
ORDER BY views DESC
|
||
LIMIT %d",
|
||
$pool
|
||
);
|
||
$rows = $wpdb->get_results($sql);
|
||
if (empty($rows)) {
|
||
return array();
|
||
}
|
||
|
||
$scored = array();
|
||
foreach ($rows as $r) {
|
||
$r->views = (int) $r->views;
|
||
$r->love = (int) $r->love;
|
||
$r->comments = (int) $r->comment_count;
|
||
$r->score = yang_hot_score($r->views, $r->love, $r->comments);
|
||
$scored[] = $r;
|
||
}
|
||
|
||
usort($scored, function ($a, $b) {
|
||
if ($a->score == $b->score) {
|
||
// 同分按浏览数兜底,保证顺序稳定(否则每次刷新顺序会跳)
|
||
return $b->views <=> $a->views;
|
||
}
|
||
return $b->score <=> $a->score;
|
||
});
|
||
|
||
return array_slice($scored, 0, $nums);
|
||
}
|
||
|
||
endif;
|
||
|
||
/**
|
||
* 小工具文章聚合 —— 热点文章
|
||
*
|
||
* ⚠️ 这个函数名和签名保留着,因为 `widget_posts::widget()` 里有一句
|
||
* `if (function_exists('most_comm_posts'))` 调用它。留着旧名不破兼容,
|
||
* 但内部已经完全换成 yang_hot_posts()。
|
||
*
|
||
* @param int $days 已废弃。旧实现用它当"最近 N 天发布"的时间窗,
|
||
* 那正是只显示一条的根因。保留参数只为不破坏调用签名。
|
||
* @param int $nums 返回条数
|
||
*/
|
||
function most_comm_posts($days = 30, $nums = 6)
|
||
{
|
||
$posts = yang_hot_posts($nums);
|
||
|
||
$output = '';
|
||
foreach ($posts as $p) {
|
||
$output .= '<a class="bookmark-item" title="' . esc_attr($p->post_title) . '"'
|
||
. ' href="' . esc_url(get_permalink($p->ID)) . '" rel="bookmark">'
|
||
. '<i class="kicon i-book"></i>'
|
||
. esc_html(wp_strip_all_tags($p->post_title))
|
||
. '</a>';
|
||
}
|
||
echo $output;
|
||
}
|
||
|
||
class widget_search extends WP_Widget {
|
||
|
||
public function __construct() {
|
||
$widget_ops = array(
|
||
'classname' => 'widget_search',
|
||
'description' => __( 'A search form for your site.' ),
|
||
'customize_selective_refresh' => true,
|
||
);
|
||
parent::__construct( 'search', _x( 'Search', 'Search widget' ), $widget_ops );
|
||
}
|
||
|
||
public function widget( $args, $instance ) {
|
||
$title = ! empty( $instance['title'] ) ? $instance['title'] : '';
|
||
$title = apply_filters( 'widget_title', $title, $instance, $this->id_base );
|
||
|
||
echo '<div class="widget w-search">';
|
||
if ( $title ) {
|
||
echo '<div class="title">'. $title .'</div>';
|
||
}
|
||
echo '<div class="item"> <form role="search" method="get" id="searchform" class="searchform" action="'. home_url('/') .'"> <div class="input-group mt-2 mb-2"> <input type="text" name="s" id="search" class="form-control" placeholder="'. __('搜点什么呢?', 'kratos') .'"> <div class="input-group-append"> <button class="btn btn-primary btn-search" type="submit" id="searchsubmit">'. __('搜索', 'kratos') .'</button> </div> </div> </form>';
|
||
echo '</div></div>';
|
||
}
|
||
|
||
public function form( $instance ) {
|
||
$instance = wp_parse_args( (array) $instance, array( 'title' => '' ) );
|
||
$title = $instance['title'];
|
||
?>
|
||
<p><label for="<?php echo $this->get_field_id( 'title' ); ?>"><?php _e( 'Title:' ); ?> <input class="widefat" id="<?php echo $this->get_field_id( 'title' ); ?>" name="<?php echo $this->get_field_name( 'title' ); ?>" type="text" value="<?php echo esc_attr( $title ); ?>" /></label></p>
|
||
<?php
|
||
}
|
||
|
||
public function update( $new_instance, $old_instance ) {
|
||
$instance = $old_instance;
|
||
$new_instance = wp_parse_args( (array) $new_instance, array( 'title' => '' ) );
|
||
$instance['title'] = sanitize_text_field( $new_instance['title'] );
|
||
return $instance;
|
||
}
|
||
|
||
}
|
||
|
||
class widget_ad extends WP_Widget
|
||
{
|
||
public function __construct()
|
||
{
|
||
add_action('admin_enqueue_scripts', array($this, 'scripts'));
|
||
|
||
$widget_ops = array(
|
||
'name' => __('图片广告', 'kratos'),
|
||
'description' => __('显示自定义图片广告的工具', 'kratos'),
|
||
);
|
||
|
||
parent::__construct(false, false, $widget_ops);
|
||
}
|
||
|
||
public function scripts()
|
||
{
|
||
wp_enqueue_script('media-upload');
|
||
wp_enqueue_media();
|
||
wp_enqueue_script('widget_scripts', ASSET_PATH . '/assets/js/widget.min.js', array('jquery'));
|
||
wp_enqueue_style('widget_css', ASSET_PATH . '/assets/css/widget.min.css', array());
|
||
}
|
||
|
||
public function widget($args, $instance)
|
||
{
|
||
$subtitle = !empty($instance['subtitle']) ? $instance['subtitle'] : __('广告', 'kratos');
|
||
$image = !empty($instance['image']) ? $instance['image'] : '';
|
||
$url = !empty($instance['url']) ? $instance['url'] : '';
|
||
|
||
echo '<div class="widget w-ad">';
|
||
echo '<a href="' . $url . '" target="_blank" rel="noreferrer"><img src="' . $image . '"><div class="prompt">' . $subtitle . '</div></a>';
|
||
echo '</div>';
|
||
}
|
||
|
||
public function update($new_instance, $old_instance)
|
||
{
|
||
$instance = array();
|
||
|
||
$instance['subtitle'] = (!empty($new_instance['subtitle'])) ? $new_instance['subtitle'] : '';
|
||
$instance['image'] = (!empty($new_instance['image'])) ? $new_instance['image'] : '';
|
||
$instance['url'] = (!empty($new_instance['url'])) ? $new_instance['url'] : '';
|
||
|
||
return $instance;
|
||
}
|
||
|
||
public function form($instance)
|
||
{
|
||
$subtitle = !empty($instance['subtitle']) ? $instance['subtitle'] : __('广告', 'kratos');
|
||
$image = !empty($instance['image']) ? $instance['image'] : '';
|
||
$url = !empty($instance['url']) ? $instance['url'] : '';
|
||
?>
|
||
<div class="media-widget-control">
|
||
<p>
|
||
<label for="<?php echo $this->get_field_id('subtitle'); ?>"><?php _e('副标题:', 'kratos');?></label>
|
||
<input class="widefat" id="<?php echo $this->get_field_id('subtitle'); ?>" name="<?php echo $this->get_field_name('subtitle'); ?>" type="text" value="<?php echo esc_attr($subtitle); ?>">
|
||
</p>
|
||
<p>
|
||
<label for="<?php echo $this->get_field_id('url'); ?>"><?php _e('链接地址:', 'kratos');?></label>
|
||
<input class="widefat" id="<?php echo $this->get_field_id('url'); ?>" name="<?php echo $this->get_field_name('url'); ?>" type="text" value="<?php echo esc_attr($url); ?>">
|
||
</p>
|
||
<p>
|
||
<label for="<?php echo $this->get_field_id('image'); ?>"><?php _e('广告图片:', 'kratos');?></label>
|
||
<input class="widefat" id="<?php echo $this->get_field_id('image'); ?>" name="<?php echo $this->get_field_name('image'); ?>" type="text" value="<?php echo esc_url($image); ?>" />
|
||
<button type="button" class="button-update-media upload_ad"><?php _e('选择图片', 'kratos');?></button>
|
||
</p>
|
||
</div>
|
||
<?php
|
||
}
|
||
}
|
||
|
||
class widget_about extends WP_Widget
|
||
{
|
||
public function __construct()
|
||
{
|
||
add_action('admin_enqueue_scripts', array($this, 'scripts'));
|
||
|
||
$widget_ops = array(
|
||
'name' => __('个人简介', 'kratos'),
|
||
'description' => __('可跳转后台的个人简介展示工具', 'kratos'),
|
||
);
|
||
|
||
parent::__construct(false, false, $widget_ops);
|
||
}
|
||
|
||
public function scripts()
|
||
{
|
||
wp_enqueue_script('media-upload');
|
||
wp_enqueue_media();
|
||
wp_enqueue_script('widget_scripts', ASSET_PATH . '/assets/js/widget.min.js', array('jquery'));
|
||
wp_enqueue_style('widget_css', ASSET_PATH . '/assets/css/widget.min.css', array());
|
||
}
|
||
|
||
public function widget($args, $instance)
|
||
{
|
||
$introduce = kratos_option('a_about', __('保持饥渴的专注,追求最佳的品质', 'kratos'));
|
||
$username = kratos_option('a_nickname', __('Kratos', 'kratos'));
|
||
$avatar = yang_option_image('a_gravatar');
|
||
$background = !empty($instance['background']) ? $instance['background'] : ASSET_PATH . '/assets/img/about-background.png';
|
||
|
||
echo '<div class="widget w-about">';
|
||
echo '<div class="background" style="background:url(' . $background . ') no-repeat center center;-webkit-background-size: cover; -moz-background-size: cover; -o-background-size: cover; background-size: cover;"></div><div class="wrapper text-center">';
|
||
if (current_user_can('manage_options')) {
|
||
echo '<a href="' . admin_url() . '">';
|
||
} else {
|
||
echo '<a href="' . wp_login_url() . '">';
|
||
}
|
||
echo '<img src="' . $avatar . '"></a>';
|
||
echo '</div><div class="textwidget text-center"><p class="username">'. $username .'</p><p class="about">' . $introduce . '</p></div>';
|
||
echo '</div>';
|
||
}
|
||
|
||
public function update($new_instance, $old_instance)
|
||
{
|
||
$instance = array();
|
||
|
||
$instance['background'] = (!empty($new_instance['background'])) ? $new_instance['background'] : '';
|
||
|
||
return $instance;
|
||
}
|
||
|
||
public function form($instance)
|
||
{
|
||
$background = !empty($instance['background']) ? $instance['background'] : ASSET_PATH . '/assets/img/about-background.png';
|
||
?>
|
||
<div class="media-widget-control">
|
||
<p>
|
||
<label for="<?php echo $this->get_field_id('background'); ?>"><?php _e('背景图片:', 'kratos');?></label>
|
||
<input class="widefat" id="<?php echo $this->get_field_id('background'); ?>" name="<?php echo $this->get_field_name('background'); ?>" type="text" value="<?php echo esc_attr($background); ?>">
|
||
<button type="button" class="button-update-media upload_background"><?php _e('选择图片', 'kratos');?></button>
|
||
</p>
|
||
</div>
|
||
<?php
|
||
}
|
||
}
|
||
|
||
class widget_tags extends WP_Widget
|
||
{
|
||
public function __construct()
|
||
{
|
||
$widget_ops = array(
|
||
'name' => __('标签聚合', 'kratos'),
|
||
'description' => __('文章标签的展示工具', 'kratos'),
|
||
);
|
||
|
||
parent::__construct(false, false, $widget_ops);
|
||
}
|
||
|
||
public function widget($args, $instance)
|
||
{
|
||
$number = !empty($instance['number']) ? $instance['number'] : '8';
|
||
$order = !empty($instance['order']) ? $instance['order'] : 'RAND';
|
||
$tags = wp_tag_cloud(array(
|
||
'unit' => 'px',
|
||
'smallest' => 14,
|
||
'largest' => 14,
|
||
'number' => $number,
|
||
'format' => 'flat',
|
||
'orderby' => 'count',
|
||
'order' => $order,
|
||
'echo' => false,
|
||
)
|
||
);
|
||
echo '<div class="widget w-tags">';
|
||
echo '<div class="title">' . __('标签聚合', 'kratos') . '</div>';
|
||
echo '<div class="item">' . $tags . '</div>';
|
||
echo '</div>';
|
||
}
|
||
|
||
public function update($new_instance, $old_instance)
|
||
{
|
||
$instance = array();
|
||
|
||
$instance['number'] = (!empty($new_instance['number'])) ? $new_instance['number'] : '';
|
||
$instance['order'] = (!empty($new_instance['order'])) ? $new_instance['order'] : '';
|
||
|
||
return $instance;
|
||
}
|
||
|
||
public function form($instance)
|
||
{
|
||
global $wpdb;
|
||
$number = !empty($instance['number']) ? $instance['number'] : '8';
|
||
$order = !empty($instance['order']) ? $instance['order'] : 'RAND';
|
||
?>
|
||
<div class="media-widget-control">
|
||
<p>
|
||
<label for="<?php echo $this->get_field_id('number'); ?>"><?php _e('显示数量:', 'kratos');?></label>
|
||
<input class="widefat" id="<?php echo $this->get_field_id('number'); ?>" name="<?php echo $this->get_field_name('number'); ?>" type="text" value="<?php echo esc_attr($number); ?>" />
|
||
</p>
|
||
<p>
|
||
<label for="<?php echo $this->get_field_id('order'); ?>"><?php _e('显示排序:', 'kratos');?></label>
|
||
<select name="<?php echo $this->get_field_name("order"); ?>" id='<?php echo $this->get_field_id("order"); ?>'>
|
||
<option value="DESC" <?php echo ($order == 'DESC') ? 'selected' : ''; ?>><?php _e('降序', 'kratos');?></option>
|
||
<option value="ASC" <?php echo ($order == 'ASC') ? 'selected' : ''; ?>><?php _e('升序', 'kratos');?></option>
|
||
<option value="RAND" <?php echo ($order == 'RAND') ? 'selected' : ''; ?>><?php _e('随机', 'kratos');?></option>
|
||
</select>
|
||
</p>
|
||
</div>
|
||
<?php
|
||
}
|
||
}
|
||
|
||
class widget_posts extends WP_Widget
|
||
{
|
||
public function __construct()
|
||
{
|
||
$widget_ops = array(
|
||
'name' => __('文章聚合', 'kratos'),
|
||
'description' => __('展示最热、随机、最新文章的工具', 'kratos'),
|
||
);
|
||
|
||
parent::__construct(false, false, $widget_ops);
|
||
}
|
||
|
||
public function widget($args, $instance)
|
||
{
|
||
$number = !empty($instance['number']) ? (int) $instance['number'] : 6;
|
||
if ($number < 1) {
|
||
$number = 6;
|
||
}
|
||
|
||
echo '<div class="widget w-recommended">';
|
||
?>
|
||
<div class="nav nav-tabs d-none d-xl-flex" id="nav-tab" role="tablist">
|
||
<a class="nav-item nav-link active" id="nav-new-tab" data-toggle="tab" href="#nav-new" role="tab" aria-controls="nav-new" aria-selected="false"><i class="kicon i-tabnew"></i><?php _e('最新', 'kratos');?></a>
|
||
<a class="nav-item nav-link" id="nav-hot-tab" data-toggle="tab" href="#nav-hot" role="tab" aria-controls="nav-hot" aria-selected="true"><i class="kicon i-tabhot"></i><?php _e('热点', 'kratos');?></a>
|
||
<a class="nav-item nav-link" id="nav-random-tab" data-toggle="tab" href="#nav-random" role="tab" aria-controls="nav-random" aria-selected="false"><i class="kicon i-tabrandom"></i><?php _e('随机', 'kratos');?></a>
|
||
</div>
|
||
<div class="nav nav-tabs d-xl-none" id="nav-tab" role="tablist">
|
||
<a class="nav-item nav-link active" id="nav-new-tab" data-toggle="tab" href="#nav-new" role="tab" aria-controls="nav-new" aria-selected="false"><?php _e('最新', 'kratos');?></a>
|
||
<a class="nav-item nav-link" id="nav-hot-tab" data-toggle="tab" href="#nav-hot" role="tab" aria-controls="nav-hot" aria-selected="true"><?php _e('热点', 'kratos');?></a>
|
||
<a class="nav-item nav-link" id="nav-random-tab" data-toggle="tab" href="#nav-random" role="tab" aria-controls="nav-random" aria-selected="false"><?php _e('随机', 'kratos');?></a>
|
||
</div>
|
||
<div class="tab-content" id="nav-tabContent">
|
||
<div class="tab-pane fade show active" id="nav-new" role="tabpanel" aria-labelledby="nav-new-tab">
|
||
<?php
|
||
/* 🔴 原来写的是 `'numberposts=' . $number . ' & offset=0'` ——
|
||
* 字符串里带空格,WP_Query 解析时参数名变成 `' offset'`(前面
|
||
* 有空格),`offset=0` 实际没生效。offset=0 本身是默认值,
|
||
* 所以症状看不出来,但这是错的写法。改成数组传参。 */
|
||
$myposts = get_posts(array(
|
||
'numberposts' => $number,
|
||
'offset' => 0,
|
||
));
|
||
foreach ($myposts as $post): ?>
|
||
<a class="bookmark-item" title="<?php echo esc_attr($post->post_title); ?>" href="<?php echo esc_url(get_permalink($post->ID)); ?>" rel="bookmark"><i class="kicon i-book"></i><?php echo esc_html(wp_strip_all_tags($post->post_title)) ?></a>
|
||
<?php endforeach;?>
|
||
</div>
|
||
<div class="tab-pane fade" id="nav-hot" role="tabpanel" aria-labelledby="nav-hot-tab">
|
||
<?php
|
||
/* 热点:全站累计热度(views/love/评论 三项 log 加权),
|
||
* 不再受「最近 N 天发布」的时间窗限制。原因见 most_comm_posts()
|
||
* 上方的注释块。 */
|
||
if (function_exists('most_comm_posts')) {
|
||
most_comm_posts(0, $number);
|
||
} ?>
|
||
</div>
|
||
<div class="tab-pane fade" id="nav-random" role="tabpanel" aria-labelledby="nav-random-tab">
|
||
<?php
|
||
$myposts = get_posts(array(
|
||
'numberposts' => $number,
|
||
'offset' => 0,
|
||
'orderby' => 'rand',
|
||
));
|
||
foreach ($myposts as $post): ?>
|
||
<a class="bookmark-item" title="<?php echo esc_attr($post->post_title); ?>" href="<?php echo esc_url(get_permalink($post->ID)); ?>" rel="bookmark"><i class="kicon i-book"></i><?php echo esc_html(wp_strip_all_tags($post->post_title)) ?></a>
|
||
<?php endforeach;?>
|
||
</div>
|
||
</div>
|
||
<?php echo '</div>';
|
||
}
|
||
|
||
public function update($new_instance, $old_instance)
|
||
{
|
||
$instance = array();
|
||
|
||
$instance['number'] = (!empty($new_instance['number'])) ? (int) $new_instance['number'] : 6;
|
||
|
||
/* `days` 已废弃:旧实现拿它当「最近 N 天发布」的时间窗,
|
||
* 那正是热点只显示一条的根因。这里不再保存,
|
||
* 老实例里残留的 days 值会在下次保存时被丢掉。 */
|
||
return $instance;
|
||
}
|
||
public function form($instance)
|
||
{
|
||
$number = !empty($instance['number']) ? $instance['number'] : '6';
|
||
?>
|
||
<div class="media-widget-control">
|
||
<p>
|
||
<label for="<?php echo $this->get_field_id('number'); ?>"><?php _e('展示数量:', 'kratos');?></label>
|
||
<input class="widefat" id="<?php echo $this->get_field_id('number'); ?>" name="<?php echo $this->get_field_name('number'); ?>" type="text" value="<?php echo esc_attr($number); ?>" />
|
||
</p>
|
||
<p class="description">
|
||
<?php _e('「最新」「随机」「热点」三个标签页都用这个数量。', 'kratos'); ?><br>
|
||
<?php _e('「热点」按全站累计热度排序:浏览量、点赞数、评论数三项分别取对数后加权(3 : 5 : 4),不受发布时间限制。', 'kratos'); ?>
|
||
</p>
|
||
</div>
|
||
<?php
|
||
}
|
||
}
|
||
|
||
function register_widgets()
|
||
{
|
||
register_widget('widget_ad');
|
||
register_widget('widget_about');
|
||
register_widget('widget_tags');
|
||
register_widget('widget_search');
|
||
register_widget('widget_posts');
|
||
}
|
||
add_action('widgets_init', 'register_widgets');
|