As a follow up I'd like to share the complete solution (if everyone is interested). Thanks to @Antti Konskinen and @Rup again.
/*
Plugin Name: Vote on Comments
Version:
*/
define('COMMENT_VOTE_PLUGIN_VERSION', "1.1.5");
define('COMMENT_VOTE_PLUGIN_URL', WP_PLUGIN_URL . "/" . dirname(plugin_basename(__FILE__)));
define('COMMENT_VOTE_PLUGIN_DIR', WP_PLUGIN_DIR . "/" . dirname(plugin_basename(__FILE__)));
require_once(COMMENT_VOTE_PLUGIN_DIR . '/inc/comment-vote-counts.php');
register_activation_hook( __FILE__, 'comment_vote_init_db' );
function comment_vote_init_db() {
global $wpdb;
$charset_collate = $wpdb->get_charset_collate();
$table_name = $wpdb->prefix . 'comment_vote';
$sql = "CREATE TABLE $table_name (
comment_vote_id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
post_id bigint(20) UNSIGNED NOT NULL,
comment_id bigint(20) UNSIGNED NOT NULL,
user varchar(255) COLLATE utf8mb4_unicode_520_ci NOT NULL,
vote tinyint(1) NOT NULL,
PRIMARY KEY (comment_vote_id),
KEY post_id_comment_id_vote (post_id, comment_id, vote),
UNIQUE post_id_comment_id_user (comment_id, user),
FOREIGN KEY (comment_id) REFERENCES xre2_comments(comment_ID) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (post_id) REFERENCES xre2_posts(ID) ON DELETE CASCADE ON UPDATE CASCADE
) $charset_collate;";
require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
dbDelta( $sql );
}
class CommentVoteCounts
{
protected array $votes;
public function __construct(array $votes)
{
$this->votes = $votes;
}
public function provide_votes(int $value, int $comment_id): int
{
return $this->votes[$comment_id] ?? 0;
}
}
function comment_vote_enqueuescripts()
{
wp_enqueue_style('comment-vote', COMMENT_VOTE_PLUGIN_URL . '/assets/css/comment-vote.css', array(), COMMENT_VOTE_PLUGIN_VERSION);
wp_enqueue_script('comment-vote', COMMENT_VOTE_PLUGIN_URL . '/assets/js/comment-vote.js', array('jquery'), COMMENT_VOTE_PLUGIN_VERSION);
wp_localize_script('comment-vote', 'comment_vote_ajax', array('ajaxurl' => admin_url('admin-ajax.php')));
}
add_action('wp_enqueue_scripts', 'comment_vote_enqueuescripts');
function comment_vote_text($comment_text, $comment, $args)
{
if ($comment->comment_approved) {
$vote_link = comment_vote_showlink();
return $comment_text . $vote_link;
}
return $comment_text;
}
add_filter('comment_text', 'comment_vote_text', 10, 3);
function comment_vote_showlink()
{
error_log("Plugin URL: " . COMMENT_VOTE_PLUGIN_URL);
$nonce = wp_create_nonce("comment_vote_nonce");
$post_id = get_the_ID();
$comment_id = get_comment_ID();
$arguments = "'" . $nonce . "', " . $post_id . ", " . $comment_id;
$link_up = '<a class="comment-vote-thumb" href="javascript:commentVote(' . $arguments . ", true" . ');"><span class="material-symbols-outlined">thumb_up</span></a>';
$link_down = '<a class="comment-vote-thumb" href="javascript:commentVote(' . $arguments . ", false" . ');"><span class="material-symbols-outlined">thumb_down</span></a>';
$label_votes = comment_vote_show_votes();
$completelink = '<div class="comment-vote" id="comment-vote-' . $comment_id . '">';
$completelink .= $link_up . $label_votes . $link_down;
$completelink .= '<img id="comment-vote-loading-' . $comment_id . '" class="comment-vote-loading" style="display: none" src="' . COMMENT_VOTE_PLUGIN_URL . '/assets/images/loading.gif?ver' . COMMENT_VOTE_PLUGIN_VERSION . '" />';
$completelink .= '</div>';
return $completelink;
}
function comment_vote_ajaxhandler()
{
if (!wp_verify_nonce($_POST['nonce'], "comment_vote_nonce")) {
wp_send_json_error("Invalid Nonce");
}
if (!isset($_POST['post_id']) || !isset($_POST['comment_id']) || !isset($_POST['user']) || !isset($_POST['up'])) {
wp_send_json_error('Missing params');
}
$up = $_POST['up'];
if ($up != 'true' && $up != 'false') {
wp_send_json_error("Invalid value for up param: [" . $up . "]");
}
$post_id = $_POST['post_id'];
$comment_id = $_POST['comment_id'];
$user = $_POST['user'];
$new_votes = null;
try {
$vote = filter_var($up, FILTER_VALIDATE_BOOLEAN);
$new_votes = vote($post_id, $comment_id, $user, $vote);
} catch (Exception $e) {
wp_send_json_error("Error: " . (string)$e);
}
if (!is_null($new_votes)) {
wp_send_json_success($new_votes);
}
wp_send_json_error("Something didn't work.");
}
add_action('wp_ajax_nopriv_comment_vote_ajaxhandler', 'comment_vote_ajaxhandler');
add_action('wp_ajax_comment_vote_ajaxhandler', 'comment_vote_ajaxhandler');
function vote($post_id, $comment_id, $user, $vote): int
{
global $wpdb;
$table = $wpdb->prefix . 'comment_vote';
$result = $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT(*) FROM $table WHERE post_id=%s AND comment_id=%s AND user=%s",
$post_id,
$comment_id,
$user
)
);
$exists = $result > 0;
if (!$exists) {
$data = array(
'post_id' => $post_id,
'comment_id' => $comment_id,
'user' => $user,
'vote' => ($vote ? '1' : '-1')
);
$wpdb->insert($table, $data);
update_votes_cache($post_id);
}
$comment_vote_counts = get_comment_vote_counts_for_post($post_id);
$sum_votes = $comment_vote_counts[$comment_id];
return $sum_votes;
}
function get_all_votes(int $post_id)
{
global $wpdb;
$table = $wpdb->prefix . 'comment_vote';
$sql_query = <<<EOD
SELECT comment_id, SUM(vote) AS votes
FROM $table
WHERE post_id = '$post_id'
GROUP BY comment_id;
EOD;
$results = $wpdb->get_results($sql_query);
$arr = array();
foreach ($results as $result) {
$arr[$result->comment_id] = $result->votes;
}
return $arr;
}
// get the data or query and calculate it
function get_comment_vote_counts_for_post(int $post_id): array
{
$counts = get_transient("comment_vote_counts_{$post_id}");
if ($counts && is_array($counts)) {
return $counts;
}
return update_votes_cache($post_id);
}
function update_votes_cache(int $post_id): array
{
$vote_counts = get_all_votes($post_id);
set_transient("comment_vote_counts_{$post_id}", $vote_counts, 1 * MINUTE_IN_SECONDS);
return $vote_counts;
}
// setup the data provider
function provide_post_comments_vote_counts(int $post_id): void
{
$vote_counts = new CommentVoteCounts(
get_comment_vote_counts_for_post($post_id)
);
add_filter('comment_vote_counts', [$vote_counts, 'provide_votes'], 10, 2);
}
// retrieve the votes data by applying a filter
function comment_vote_show_votes(): string
{
$comment_id = get_comment_ID();
$sum_votes = apply_filters('comment_vote_counts', 0, $comment_id);
return '<div id="comment-vote-votes-' . $comment_id . '" class="comment-vote-votes comment-vote-' . ($sum_votes == 0 ? 'neu' : ($sum_votes > 0 ? 'pos' : 'neg')) . '">' . $sum_votes . '</div>';
}
// setup the provider at some suitable point of the WordPress lifecycle, e.g.
add_action('template_redirect', 'set_up_post_comment_vote_counts');
function set_up_post_comment_vote_counts(): void
{
if (!is_single()) {
return;
}
provide_post_comments_vote_counts(get_the_ID());
}