Thêm widget Top Bình luận cho website WordPress

Bài viết này giới thiệu đến bạn plugin để thêm widget Top Bình luận cho website WordPress, giao diện giống Hocban.vn đang dùng, và code đã tối ưu cho PHP 8.4 | Nếu bạn muốn khích lệ người dùng để lại bình luận trên web, blog thì đây là một cách hay đó nhen. Mình bổ sung thêm từ khoá để ae tìm Google hoặc thanh tìm kiếm của Hocban.vn cho dễ: Plugin Top Comment, Widget Top Comment.

Widget Top Bình luận cho website WordPress
Widget Top Bình luận cho website WordPress

Để thực hiện thì bạn cứ cài plugin Top Commentators Widget trong thư viện plugin của WordPress như bình thường. Hôm qua tác giả thấy có cập nhật bản mới (20/12/2025) nhưng mình thấy vẫn chưa ổn. Thậm chí là cảnh báo khả năng tương thích.

Nhưng không sao, bạn cứ cài đi rồi sử dụng code thay thế của mình bên dưới là được. Nhớ nâng phiên bản php 8.4 lên cho tương thích hoàn toàn, dùng luôn theme GeneratePress nữa thì ngon luôn. Plugin này khá đơn giản, nó chỉ có duy nhất 1 file code php là top-commentators-widget.php >> bạn xoá code bên trong và dán đè code bên dưới vào.

<?php
/*
Plugin Name: Top Commentators Widget
Description: Adds a sidebar widget to show the top commentators in your WP site. Adapted from Show Top Commentators plugin.
Author: Lorna Timbah (webgrrrl)
Version: 1.8.0
Author URI: http://webgrrrl.net
Plugin URI: http://webgrrrl.net/archives/my-top-commentators-widget-quick-dirty.htm
Text Domain: top-commentators-widget
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
*/
function topcomm_load_widgets() {
register_widget('Topcomm_Widget');
}
add_action('widgets_init', 'topcomm_load_widgets');
class Topcomm_Widget extends WP_Widget {
function __construct() {
$widget_ops = array('classname' => 'topcomm', 'description' => __('Shows the top commentators in your blog.', 'topcomm'));
parent::__construct('topcomm-widget', __('Top Commentators Widget', 'topcomm'), $widget_ops);
}
function widget($args, $instance) {
extract($args);
$title = $instance['title'];
$listDesc = $instance['listDesc'];
$minComments = "";
$new_names = "";
$filterUrl = "";
$filterEmail = "";
$countList = 0;
if (isset($instance['minComments']) || !empty($instance['minComments'])) {
$minComments = " HAVING comment_comments >= " . intval($instance['minComments']);
}
if ($instance['excludeNames'] != "") {
$excludeNames = trim($instance['excludeNames']);
$excludeNames = explode(",", $excludeNames);
for ($i = 0; $i < count($excludeNames); $i++) {
$new_names .= " AND comment_author NOT IN ('" . trim($excludeNames[$i]) . "')";
}
$excludeNames = $new_names;
}
$listPeriod = $instance['listPeriod'];
switch ($listPeriod) {
case "h": $listPeriod = "DATE_FORMAT(comment_date, '%Y-%m-%d %H') = DATE_FORMAT(CURDATE(), '%Y-%m-%d %H')"; break;
case "d": $listPeriod = "DATE_FORMAT(comment_date, '%Y-%m-%d') = DATE_FORMAT(CURDATE(), '%Y-%m-%d')"; break;
case "w": $listPeriod = "DATE_FORMAT(comment_date, '%Y-%v') = DATE_FORMAT(CURDATE(), '%Y-%v')"; break;
case "m": $listPeriod = "DATE_FORMAT(comment_date, '%Y-%m') = DATE_FORMAT(CURDATE(), '%Y-%m')"; break;
case "y": $listPeriod = "DATE_FORMAT(comment_date, '%Y') = DATE_FORMAT(CURDATE(), '%Y')"; break;
case "a": $listPeriod = "1=1"; break;
case is_numeric($listPeriod): $listPeriod = "comment_date >= CURDATE() - INTERVAL $listPeriod DAY"; break;
case strpos($listPeriod, 'and') !== false: $listPeriod = "comment_date BETWEEN $listPeriod"; break;
default: $listPeriod = "comment_date >= CURDATE() - INTERVAL 30 DAY"; break;
}
$listPeriod = ' AND ' . $listPeriod;
$limitList = $instance['limitList'];
$limitChar = $instance['limitChar'];
$listNull = $instance['listNull'];
if ($instance['filterUrl'] != "") {
$filterUrl = trim($instance['filterUrl']);
$filterUrl = explode(",", $filterUrl);
$new_urls = "";
for ($i = 0; $i < count($filterUrl); $i++) {
$new_urls .= " AND comment_author_url NOT LIKE '%" . trim($filterUrl[$i]) . "%'";
}
$filterUrl = $new_urls;
}
if ($instance['filterEmail'] != "") {
$filterEmail = trim($instance['filterEmail']);
$filterEmail = explode(",", $filterEmail);
$new_emails = "";
for ($i = 0; $i < count($filterEmail); $i++) {
$new_emails .= " AND comment_author_email NOT LIKE '%" . trim($filterEmail[$i]) . "%'";
}
$filterEmail = $new_emails;
}
$listType = $instance['listType'];
if ($listType == "num") { $listStart = "<ol>"; $listEnd = "</ol>"; } else { $listStart = "<ul>"; $listEnd = "</ul>"; }
$makeLink = $instance['makeLink'];
$targetBlank = $instance['targetBlank'];
$noFollow = $instance['noFollow'];
$showCount = $instance['showCount'];
$groupBy = ($instance['groupBy'] == "1") ? "comment_author" : "comment_author_email";
$showInHome = $instance['showInHome'];
$onlyWithUrl = ($instance['onlyWithUrl'] == '1') ? " AND comment_author_url != '' AND comment_author_url != 'http://'" : '';
$displayGravatar = $instance['displayGravatar'];
$defaultGravatar = $instance['defaultGravatar'];
$avatarSize = $instance['avatarSize'];
$displayAward = $instance['displayAward'];
$iconAward = $instance['iconAward'];
$alignAward = $instance['alignAward'];
$writeList = "\n" . $before_widget . "\n" . $before_title . $title . $after_title;
$writeList .= $listDesc . "\n" . $listStart . "\n";
global $wpdb;
$commenters = $wpdb->get_results("SELECT COUNT($groupBy) AS comment_comments, comment_author, comment_author_url, comment_author_email FROM $wpdb->comments WHERE comment_type != 'pingback' AND comment_author != '' AND comment_approved = '1' $excludeNames $listPeriod $filterUrl $filterEmail $onlyWithUrl GROUP BY $groupBy $minComments ORDER BY comment_comments DESC, comment_author");
if (count($commenters) > 0) {
$commenters = array_slice($commenters, 0, $limitList);
foreach ($commenters as $k) {
$url = $wpdb->get_var("SELECT comment_author_url FROM $wpdb->comments WHERE $groupBy = '" . addslashes($k->$groupBy) . "' AND comment_author_url != 'http://' AND comment_approved = 1 ORDER BY comment_date DESC LIMIT 1");
$writeList .= '<li>';
if (!empty($url) && $makeLink == 1) {
$writeList .= "<a href='" . esc_url($url) . "'";
if ($noFollow == 1) $writeList .= " rel='nofollow'";
if ($targetBlank == 1) $writeList .= " target='_blank'";
$writeList .= ">";
}
$nComments = (int)$k->comment_comments;
$strAward = ($displayAward != '0' && $nComments >= $displayAward) ? '<img class="tcwAward" src="' . esc_url($iconAward) . '" alt="Award" title="Award" /> ' : '';
if ($alignAward == 0) $writeList .= $strAward;
if ($displayGravatar == 1) {
$image = md5(strtolower($k->comment_author_email));
$defavatar = urlencode($defaultGravatar);
$writeList .= '<img class="tcwGravatar" src="https://www.gravatar.com/avatar/' . $image . '?size=' . $avatarSize . '&default=' . $defavatar . '" alt ="' . esc_attr($k->comment_author) . '" title="' . esc_attr($k->comment_author) . '" /> ';
}
if ($alignAward == 1) $writeList .= $strAward;
$str = (strlen($k->comment_author) > $limitChar) ? substr($k->comment_author, 0, $limitChar) . "..." : $k->comment_author;
$writeList .= esc_html($str);
if ($showCount == 1) $writeList .= ' (' . $nComments . ')';
if (!empty($url) && $makeLink == 1) $writeList .= "</a>";
if ($alignAward == 2) $writeList .= $strAward;
$writeList .= "</li>\n";
$countList++;
}
} else {
$writeList .= "<li>" . esc_html($listNull) . "</li>\n";
}
$writeList .= $listEnd . "\n" . $after_widget . "\n";
if ($showInHome == 1) { if (is_home()) echo $writeList; } else { echo $writeList; }
}
function update($new_instance, $old_instance) {
$instance = $old_instance;
$instance['title'] = wp_strip_tags($new_instance['title']);
$instance['listDesc'] = wp_kses_post($new_instance['listDesc']);
$instance['minComments'] = intval($new_instance['minComments']);
$instance['excludeNames'] = wp_strip_tags($new_instance['excludeNames']);
$instance['listPeriod'] = !empty($new_instance['listPeriodnum']) ? sanitize_text_field($new_instance['listPeriodnum']) : sanitize_text_field($new_instance['listPeriod']);
$instance['limitList'] = intval($new_instance['limitList']);
$instance['limitChar'] = intval($new_instance['limitChar']);
$instance['listNull'] = wp_strip_tags($new_instance['listNull']);
$instance['filterUrl'] = wp_strip_tags($new_instance['filterUrl']);
$instance['filterEmail'] = wp_strip_tags($new_instance['filterEmail']);
$instance['listType'] = sanitize_text_field($new_instance['listType']);
$instance['makeLink'] = intval($new_instance['makeLink']);
$instance['targetBlank'] = intval($new_instance['targetBlank']);
$instance['noFollow'] = intval($new_instance['noFollow']);
$instance['showCount'] = intval($new_instance['showCount']);
$instance['groupBy'] = intval($new_instance['groupBy']);
$instance['showInHome'] = intval($new_instance['showInHome']);
$instance['onlyWithUrl'] = intval($new_instance['onlyWithUrl']);
$instance['displayGravatar'] = intval($new_instance['displayGravatar']);
$instance['defaultGravatar'] = sanitize_text_field($new_instance['defaultGravatar']);
$instance['avatarSize'] = intval($new_instance['avatarSize']);
$instance['displayAward'] = intval($new_instance['displayAward']);
$instance['iconAward'] = empty($new_instance['iconAward']) ? 'https://imagizer.imageshack.us/a/img923/4751/wUoZst.png' : esc_url_raw($new_instance['iconAward']);
$instance['alignAward'] = intval($new_instance['alignAward']);
return $instance;
}
function form($instance) {
$defaults = array('title' => 'Top Commentators', 'listDesc' => '', 'minComments' => '0', 'excludeNames' => 'admin', 'listPeriod' => 'm', 'limitList' => '10', 'limitChar' => '20', 'listNull' => 'Be the first to comment.', 'filterUrl' => '', 'filterEmail' => '', 'listType' => 'bul', 'makeLink' => '1', 'targetBlank' => '1', 'noFollow' => '0', 'showCount' => '1', 'groupBy' => '0', 'showInHome' => '1', 'onlyWithUrl' => '0', 'displayGravatar' => '1', 'defaultGravatar' => 'mm', 'avatarSize' => '20', 'displayAward' => '0', 'iconAward' => 'https://lh3.googleusercontent.com/_gE22WSc7tcQ/TVZOTOGQ66I/AAAAAAAAABg/1mAYCyHmMpw/s800/medal_icon.jpg', 'alignAward' => '2');
$instance = wp_parse_args((array)$instance, $defaults);
?>
<p><label for="<?php echo $this->get_field_id('title'); ?>">Change widget title:</label><input id="<?php echo $this->get_field_id('title'); ?>" name="<?php echo $this->get_field_name('title'); ?>" value="<?php echo esc_attr($instance['title']); ?>" style="width: 100%;" type="text" /></p>
<p><label for="<?php echo $this->get_field_id('listDesc'); ?>">Add description below the title:</label><input id="<?php echo $this->get_field_id('listDesc'); ?>" name="<?php echo $this->get_field_name('listDesc'); ?>" value="<?php echo esc_attr($instance['listDesc']); ?>" style="width: 100%;" type="text" /><br /><small>Leave blank to exclude description</small></p>
<p><label for="<?php echo $this->get_field_id('minComments'); ?>">List those with a minimum of </label><input id="<?php echo $this->get_field_id('minComments'); ?>" name="<?php echo $this->get_field_name('minComments'); ?>" value="<?php echo esc_attr($instance['minComments']); ?>" style="width: 30px;" type="text" /> comments</p>
<p><label for="<?php echo $this->get_field_id('excludeNames'); ?>">Exclude these users:</label><input id="<?php echo $this->get_field_id('excludeNames'); ?>" name="<?php echo $this->get_field_name('excludeNames'); ?>" value="<?php echo esc_attr($instance['excludeNames']); ?>" style="width: 100%;" type="text" /><br /><small>Separate each name with a comma (,)</small></p>
<p><label for="<?php echo $this->get_field_id('listPeriod'); ?>">Reset period every:</label>
<select id="<?php echo $this->get_field_id('listPeriod'); ?>" name="<?php echo $this->get_field_name('listPeriod'); ?>">
<option value="h" <?php selected($instance['listPeriod'], 'h'); ?>>Hour</option>
<option value="d" <?php selected($instance['listPeriod'], 'd'); ?>>Day</option>
<option value="w" <?php selected($instance['listPeriod'], 'w'); ?>>Week</option>
<option value="m" <?php selected($instance['listPeriod'], 'm'); ?>>Month</option>
<option value="y" <?php selected($instance['listPeriod'], 'y'); ?>>Year</option>
<option value="a" <?php selected($instance['listPeriod'], 'a'); ?>>List all</option>
</select><br />Or specify number of days / enter range of date: <input style="width: 100%;" id="<?php echo $this->get_field_id('listPeriodnum'); ?>" name="<?php echo $this->get_field_name('listPeriodnum'); ?>" type="text" value="" /><br /><small>E.g. 100 for # of days or 20090301 and 20090531 for date range</small></p>
<p><label for="<?php echo $this->get_field_id('limitList'); ?>">Limit number of names to:</label><input id="<?php echo $this->get_field_id('limitList'); ?>" name="<?php echo $this->get_field_name('limitList'); ?>" value="<?php echo esc_attr($instance['limitList']); ?>" style="width: 30px;" type="text" /><br /><small>Enter numbers only</small></p>
<p><label for="<?php echo $this->get_field_id('limitChar'); ?>">Limit characters in names to:</label><input id="<?php echo $this->get_field_id('limitChar'); ?>" name="<?php echo $this->get_field_name('limitChar'); ?>" value="<?php echo esc_attr($instance['limitChar']); ?>" style="width: 30px;" type="text" /><br /><small>Enter numbers only</small></p>
<p><label for="<?php echo $this->get_field_id('listNull'); ?>">Remarks for blank list:</label><input id="<?php echo $this->get_field_id('listNull'); ?>" name="<?php echo $this->get_field_name('listNull'); ?>" value="<?php echo esc_attr($instance['listNull']); ?>" style="width: 100%;" type="text" /></p>
<p><label for="<?php echo $this->get_field_id('filterUrl'); ?>">Filter the following full/partial URLs:</label><input id="<?php echo $this->get_field_id('filterUrl'); ?>" name="<?php echo $this->get_field_name('filterUrl'); ?>" value="<?php echo esc_attr($instance['filterUrl']); ?>" style="width: 100%;" type="text" /><br /><small>Separate each URL with a comma (,)</small></p>
<p><label for="<?php echo $this->get_field_id('filterEmail'); ?>">Filter the following full/partial emails:</label><input id="<?php echo $this->get_field_id('filterEmail'); ?>" name="<?php echo $this->get_field_name('filterEmail'); ?>" value="<?php echo esc_attr($instance['filterEmail']); ?>" style="width: 100%;" type="text" /><br /><small>Separate each email with a comma (,)</small></p>
<p><label for="<?php echo $this->get_field_id('listType'); ?>">Display list as:</label>
<select id="<?php echo $this->get_field_id('listType'); ?>" name="<?php echo $this->get_field_name('listType'); ?>">
<option value="bul" <?php selected($instance['listType'], 'bul'); ?>>Bulleted</option>
<option value="num" <?php selected($instance['listType'], 'num'); ?>>Numbered</option>
</select></p>
<p><label for="<?php echo $this->get_field_id('makeLink'); ?>">Hyperlink each name?</label>
<select id="<?php echo $this->get_field_id('makeLink'); ?>" name="<?php echo $this->get_field_name('makeLink'); ?>">
<option value="0" <?php selected($instance['makeLink'], '0'); ?>>No</option>
<option value="1" <?php selected($instance['makeLink'], '1'); ?>>Yes</option>
</select></p>
<p><label for="<?php echo $this->get_field_id('targetBlank'); ?>">Open each link in a new window?</label>
<select id="<?php echo $this->get_field_id('targetBlank'); ?>" name="<?php echo $this->get_field_name('targetBlank'); ?>">
<option value="0" <?php selected($instance['targetBlank'], '0'); ?>>No</option>
<option value="1" <?php selected($instance['targetBlank'], '1'); ?>>Yes</option>
</select></p>
<p><label for="<?php echo $this->get_field_id('noFollow'); ?>">NoFollow each name if hyperlinked?</label>
<select id="<?php echo $this->get_field_id('noFollow'); ?>" name="<?php echo $this->get_field_name('noFollow'); ?>">
<option value="0" <?php selected($instance['noFollow'], '0'); ?>>No</option>
<option value="1" <?php selected($instance['noFollow'], '1'); ?>>Yes</option>
</select></p>
<p><label for="<?php echo $this->get_field_id('showCount'); ?>">Show number of comments for each commenter?</label>
<select id="<?php echo $this->get_field_id('showCount'); ?>" name="<?php echo $this->get_field_name('showCount'); ?>">
<option value="0" <?php selected($instance['showCount'], '0'); ?>>No</option>
<option value="1" <?php selected($instance['showCount'], '1'); ?>>Yes</option>
</select></p>
<p><label for="<?php echo $this->get_field_id('groupBy'); ?>">(Hijack-proof?) Group commentors based on:</label>
<select id="<?php echo $this->get_field_id('groupBy'); ?>" name="<?php echo $this->get_field_name('groupBy'); ?>">
<option value="0" <?php selected($instance['groupBy'], '0'); ?>>E-mail</option>
<option value="1" <?php selected($instance['groupBy'], '1'); ?>>User name</option>
</select></p>
<p><label for="<?php echo $this->get_field_id('showInHome'); ?>">Show in home page only?</label>
<select id="<?php echo $this->get_field_id('showInHome'); ?>" name="<?php echo $this->get_field_name('showInHome'); ?>">
<option value="0" <?php selected($instance['showInHome'], '0'); ?>>No</option>
<option value="1" <?php selected($instance['showInHome'], '1'); ?>>Yes</option>
</select></p>
<p><label for="<?php echo $this->get_field_id('onlyWithUrl'); ?>">Display only commentors with URL?</label>
<select id="<?php echo $this->get_field_id('onlyWithUrl'); ?>" name="<?php echo $this->get_field_name('onlyWithUrl'); ?>">
<option value="0" <?php selected($instance['onlyWithUrl'], '0'); ?>>No</option>
<option value="1" <?php selected($instance['onlyWithUrl'], '1'); ?>>Yes</option>
</select></p>
<p><label for="<?php echo $this->get_field_id('displayGravatar'); ?>">Display Gravatar?</label>
<select id="<?php echo $this->get_field_id('displayGravatar'); ?>" name="<?php echo $this->get_field_name('displayGravatar'); ?>">
<option value="0" <?php selected($instance['displayGravatar'], '0'); ?>>No</option>
<option value="1" <?php selected($instance['displayGravatar'], '1'); ?>>Yes</option>
</select></p>
<p><label for="<?php echo $this->get_field_id('defaultGravatar'); ?>">Use the following default Gravatar:</label>
<select id="<?php echo $this->get_field_id('defaultGravatar'); ?>" name="<?php echo $this->get_field_name('defaultGravatar'); ?>">
<option value="" <?php selected($instance['defaultGravatar'], ''); ?>>Default blue</option>
<option value="404" <?php selected($instance['defaultGravatar'], '404'); ?>>404</option>
<option value="mm" <?php selected($instance['defaultGravatar'], 'mm'); ?>>Mystery Man</option>
<option value="identicon" <?php selected($instance['defaultGravatar'], 'identicon'); ?>>Identicon</option>
<option value="monsterid" <?php selected($instance['defaultGravatar'], 'monsterid'); ?>>MonsterID</option>
<option value="wavatar" <?php selected($instance['defaultGravatar'], 'wavatar'); ?>>Wavatar</option>
<option value="retro" <?php selected($instance['defaultGravatar'], 'retro'); ?>>Retro</option>
<option value="blank" <?php selected($instance['defaultGravatar'], 'blank'); ?>>Blank</option>
</select> Size:<input id="<?php echo $this->get_field_id('avatarSize'); ?>" name="<?php echo $this->get_field_name('avatarSize'); ?>" value="<?php echo esc_attr($instance['avatarSize']); ?>" style="width: 30px;" type="text" /></p>
<p><label for="<?php echo $this->get_field_id('displayAward'); ?>">Award those with at least</label><input id="<?php echo $this->get_field_id('displayAward'); ?>" name="<?php echo $this->get_field_name('displayAward'); ?>" value="<?php echo esc_attr($instance['displayAward']); ?>" style="width: 30px;" type="text" /> comments<br /><small>Award image/icon appears if number greater than zero (0)</small></p>
<p><label for="<?php echo $this->get_field_id('iconAward'); ?>">Award icon/image file location:</label><input id="<?php echo $this->get_field_id('iconAward'); ?>" name="<?php echo $this->get_field_name('iconAward'); ?>" value="<?php echo esc_url($instance['iconAward']); ?>" style="width: 100%;" type="text" /></p>
<p><label for="<?php echo $this->get_field_id('alignAward'); ?>">Align the Award icon:</label>
<select id="<?php echo $this->get_field_id('alignAward'); ?>" name="<?php echo $this->get_field_name('alignAward'); ?>">
<option value="0" <?php selected($instance['alignAward'], '0'); ?>>Left before Gravatar</option>
<option value="1" <?php selected($instance['alignAward'], '1'); ?>>Left after Gravatar</option>
<option value="2" <?php selected($instance['alignAward'], '2'); ?>>Right</option>
</select></p>
<?php
}
}

Plugin này tiện cho người không chuyên, có thể tinh chỉnh nhiều thứ bên trong widget theo ý muốn, tuy nhiên code cũng hơi dài. Sắp tới mình cũng sẽ bổ sung thêm tính năng tạo widget top comment nhẹ hơn cho ae nào có nhu cầu gọn nhẹ.

Admin

Tịnh Nguyễn

Mình thích tìm hiểu về WordPress, HTML & CSS. Là tác giả của nhiều bài viết trên blog này và các video trên Kênh YouTube Hocban.vn | Bạn thể xem thêm thông tin tại mục Liên hệ nè !
5 3 đánh giá
Đánh giá bài viết
guest

10 Bình luận
Phản hồi nội tuyến
Xem tất cả bình luận
ngayhomdo
Khách

Tính để Top Comment mà chẳng có ai bình luận nên thôi. Hỳ

TruongDevs
Khách

Trước khi anh ra bài viết này là footer bên em đang dùng cũng tham khảo từ bên anh đó hehe, chuyện giờ mới kể

TruongDevs
Khách
Trả lời  Tịnh Nguyễn

Dạ dạ, để em khắc phục chỗ đó! Thanks anh nha

TruongDevs
Khách
Trả lời  Tịnh Nguyễn

Dạ mời anh qua trải nghiệm nha, em làm xong rồi

TruongDevs
Khách
Trả lời  Tịnh Nguyễn

Dạ đúng rồi, do ban đầu yêu cầu form vào không có ô input để nhập url, nhưng kể từ bây giờ sẽ có và sẽ hiện đó anh. Còn bên cột Bình luận trước đó cũng không có, nay em làm có link rồi á

TruongDevs
Khách
Trả lời  Tịnh Nguyễn

Dạ oke anh, để em xem lại