Aula: Sistema de Votação com Estrelas (Rating)
Objetivo: Criar um MU-Plugin leve que adiciona um sistema de votação de 1 a 5 estrelas em posts, páginas ou custom post types, substituindo completamente o plugin wp-postratings e similares.
Contexto: O plugin original permite que visitantes avaliem posts com estrelas, armazenando a média e o total de votos. Nosso MU-Plugin implementa as mesmas funcionalidades, com código limpo, AJAX e prevenção de votos duplicados (por IP + cookie).
Funcionalidades implementadas
- Exibição de estrelas (1 a 5) ao lado da média atual.
- Votação via AJAX (não recarrega a página).
- Armazenamento das notas como metadados do post (
_rating_average,_rating_count,_rating_total). - Prevenção de múltiplos votos por IP + cookie (expira após 24 horas – ajustável).
- Shortcode
[ratings]para exibir o sistema dentro do conteúdo. - Função que retorna o rating formatado (estrelas em HTML/CSS).
- Compatível com tema Hello Elementor e qualquer tema que respeite o loop do WordPress.
Código do MU-Plugin: neodoor-ratings.php
Crie a pasta wp-content/mu-plugins/neodoor-ratings/ e dentro dela o arquivo neodoor-ratings.php com o conteúdo abaixo.
<?php
/**
* Plugin Name: Neodoor Ratings (WP-PostRatings substitute)
* Description: Sistema de votação com estrelas (1 a 5) via AJAX, com prevenção de múltiplos votos.
* Version: 1.0
* Author: Reinaldo (Neodoor.neo)
*/
if ( ! defined( 'ABSPATH' ) ) exit;
class Neodoor_Ratings {
private static $instance = null;
public static function get_instance() {
if ( null === self::$instance ) {
self::$instance = new self();
}
return self::$instance;
}
private function __construct() {
add_shortcode( 'ratings', [ $this, 'ratings_shortcode' ] );
add_action( 'wp_ajax_neodoor_rate_post', [ $this, 'ajax_rate_post' ] );
add_action( 'wp_ajax_nopriv_neodoor_rate_post', [ $this, 'ajax_rate_post' ] );
add_action( 'wp_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
}
public function enqueue_scripts() {
if ( ! is_singular() ) return;
wp_enqueue_script( 'neodoor-ratings', '', [], '1.0', true );
wp_add_inline_script( 'neodoor-ratings', $this->inline_js() );
wp_localize_script( 'neodoor-ratings', 'neodoor_ajax', [
'ajax_url' => admin_url( 'admin-ajax.php' ),
'nonce' => wp_create_nonce( 'neodoor_rating_nonce' ),
] );
}
private function inline_js() {
return <<<JS
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('.neodoor-rating-star').forEach(star => {
star.addEventListener('click', function(e) {
e.preventDefault();
const postId = this.dataset.postId;
const rating = this.dataset.rating;
const starContainer = this.closest('.neodoor-ratings-container');
if (starContainer.classList.contains('voted')) return;
fetch(neodoor_ajax.ajax_url, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
action: 'neodoor_rate_post',
post_id: postId,
rating: rating,
nonce: neodoor_ajax.nonce
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
starContainer.classList.add('voted');
starContainer.innerHTML = data.data.html;
} else {
alert(data.data.message);
}
});
});
});
});
JS;
}
public function ratings_shortcode( $atts ) {
$post_id = get_the_ID();
if ( ! $post_id ) return '';
return $this->render_ratings( $post_id );
}
private function render_ratings( $post_id ) {
$avg = get_post_meta( $post_id, '_rating_average', true );
$count = get_post_meta( $post_id, '_rating_count', true );
if ( ! $avg ) $avg = 0;
if ( ! $count ) $count = 0;
$stars_html = '';
for ( $i = 1; $i <= 5; $i++ ) {
$class = $i <= round($avg) ? 'star-filled' : 'star-empty';
$stars_html .= '<span class="neodoor-rating-star ' . $class . '" data-post-id="' . $post_id . '" data-rating="' . $i . '">★</span>';
}
$voted_class = $this->has_user_voted( $post_id ) ? 'voted' : '';
return '<div class="neodoor-ratings-container ' . $voted_class . '" data-post-id="' . $post_id . '">
<div class="neodoor-stars">' . $stars_html . '</div>
<div class="neodoor-rating-text">' . number_format( $avg, 1 ) . ' (' . $count . ' votos)</div>
</div>';
}
private function has_user_voted( $post_id ) {
$ip = $_SERVER['REMOTE_ADDR'];
$voted = get_post_meta( $post_id, '_rating_voted_ips', true );
if ( ! is_array( $voted ) ) $voted = [];
$cookie = isset( $_COOKIE['neodoor_rated_' . $post_id] ) ? true : false;
return in_array( $ip, $voted ) || $cookie;
}
public function ajax_rate_post() {
check_ajax_referer( 'neodoor_rating_nonce', 'nonce' );
$post_id = intval( $_POST['post_id'] );
$rating = intval( $_POST['rating'] );
if ( $rating < 1 || $rating > 5 ) wp_send_json_error( [ 'message' => 'Nota inválida.' ] );
if ( $this->has_user_voted( $post_id ) ) wp_send_json_error( [ 'message' => 'Você já votou neste conteúdo.' ] );
// Atualiza métricas
$total = get_post_meta( $post_id, '_rating_total', true );
$count = get_post_meta( $post_id, '_rating_count', true );
$total = $total ? $total : 0;
$count = $count ? $count : 0;
$total += $rating;
$count++;
$new_avg = $total / $count;
update_post_meta( $post_id, '_rating_average', $new_avg );
update_post_meta( $post_id, '_rating_count', $count );
update_post_meta( $post_id, '_rating_total', $total );
// Registra voto por IP
$voted_ips = get_post_meta( $post_id, '_rating_voted_ips', true );
if ( ! is_array( $voted_ips ) ) $voted_ips = [];
$voted_ips[] = $_SERVER['REMOTE_ADDR'];
update_post_meta( $post_id, '_rating_voted_ips', $voted_ips );
// Cookie de 24h
setcookie( 'neodoor_rated_' . $post_id, '1', time() + 86400, COOKIEPATH, COOKIE_DOMAIN );
wp_send_json_success( [ 'html' => $this->render_ratings( $post_id ), 'message' => 'Voto computado!' ] );
}
}
Neodoor_Ratings::get_instance();
Estilo CSS sugerido (adicione no tema ou no MU-Plugin de CSS):
<style>
.neodoor-ratings-container {
display: flex;
align-items: center;
gap: 12px;
margin: 15px 0;
}
.neodoor-stars {
display: inline-flex;
gap: 4px;
}
.neodoor-rating-star {
font-size: 24px;
cursor: pointer;
color: #ddd;
transition: color 0.2s;
}
.neodoor-rating-star.star-filled {
color: #ffc107;
}
.neodoor-ratings-container.voted .neodoor-rating-star {
cursor: default;
pointer-events: none;
}
.neodoor-rating-text {
font-size: 14px;
color: #555;
}
</style>
Instruções de instalação
- Crie a pasta
neodoor-ratingsdentro de/wp-content/mu-plugins/. - Dentro dela, crie o arquivo
neodoor-ratings.phpcom o código acima (e adicione o CSS no seu tema ou no MU-Plugin de estilos). - No conteúdo de posts/páginas onde quiser exibir as estrelas, insira o shortcode
[ratings]. - Desative e remova o plugin
wp-postratingse outros similares.
Como testar
- Acesse um post ou página onde o shortcode foi inserido.
- Clique em uma estrela (qualquer uma, de 1 a 5).
- Após o clique, a média e o total de votos serão atualizados via AJAX, sem recarregar a página.
- Tente votar novamente (mesmo IP/mesmo cookie) – o sistema deve bloquear e exibir mensagem.
- Se necessário, limpe os cookies ou use um navegador anônimo para testar novo voto.
Personalizações avançadas
- Alterar tempo do cookie: modifique
86400(segundos) para outro valor (ex: 604800 = 7 dias). - Remover bloqueio por IP: comente as linhas que usam
_rating_voted_ips. - Estrelas com imagem ou ícone: troque o texto
★por imagens ou use Font Awesome. - Exibir apenas em certos CPTs: no shortcode, verifique
get_post_type().
Plugins substituídos
wp-postratings– totalmente substituído.- Qualquer outro plugin de avaliação por estrelas similar.
Observações finais
- O sistema armazena três metadados por post:
_rating_average,_rating_count,_rating_totale um array de IPs_rating_voted_ips. Em sites muito visitados, esse array pode crescer; recomenda-se limpar ou usar apenas cookies. - O bloqueio por IP + cookie não é 100% à prova de fraude, mas é suficiente para a maioria dos sites.
- Se você usa cache (WP Super Cache, etc.), o AJAX ainda funciona porque a requisição é feita diretamente ao admin-ajax.php, não à página cacheada.
Registro no caderno
Data:
Funcionalidade: Sistema de votação com estrelas (1 a 5) via AJAX.
Plugins substituídos: wp-postratings e similares.
Tempo de implementação: ~15 minutos.