Ten temat był już poruszany w kwietniu tutaj: https://wphp.pl/brakujace-oceny-w-opiniach-produktow-woocommerce/
Chodzi o niespójność między faktycznymi opiniami (komentarzami z meta rating) a polami meta produktu, z których WooCommerce liczy średnią (_wc_average_rating, _wc_rating_count, _wc_review_count). Na kategoriach najczęściej wyświetla się wc_get_rating_html( $product->get_average_rating() ), więc jeśli meta są rozjechane, ratingValue bywa „z kosmosu”.
Ale rozwiązanie nie do końca zadziałało więc teraz coś bardziej profesjonalnego z możliwością wykonania testów na zapleczu.
// Działa na kategoriach o slugach w $category_slugs.
add_action('init', function () {
if ( ! is_admin() || ! current_user_can('manage_woocommerce') ) return;
if ( empty($_GET['mf_fix_ratings']) ) return;
// Uruchom dopiero gdy WC jest zainicjalizowany.
if ( ! did_action('woocommerce_init') ) {
add_action('woocommerce_init', function () { do_action('mf_fix_ratings_internal'); });
return;
}
do_action('mf_fix_ratings_internal');
}, 99);
add_action('mf_fix_ratings_internal', function () {
if ( ! class_exists('WC_Comments') ) {
add_action('admin_notices', function () {
echo '<div class="notice notice-error"><p>Brak klasy WC_Comments — WooCommerce nie jest gotowy.</p></div>';
});
return;
}
// --- USTAWIENIA ---
$category_slugs = ['fensterbaenke']; // <-- podmień jeśli potrzeba
$mode = sanitize_key($_GET['mf_fix_ratings']); // 'preview' lub 'run'
// -------------------
// Pobierz ID produktów z kategorii
$ids = wc_get_products([
'status' => 'publish',
'limit' => -1,
'category' => $category_slugs,
'return' => 'ids',
]);
$rows = [];
$fixed = 0;
foreach ($ids as $id) {
$product = wc_get_product($id);
if ( ! $product ) { continue; }
// Stany "przed"
$before_avg = get_post_meta($id, '_wc_average_rating', true);
$before_rev = (int) get_post_meta($id, '_wc_review_count', true);
$before_counts = (array) get_post_meta($id, '_wc_rating_count', true);
// Spróbuj policzyć przez WC_Comments (wymaga WC_Product, nie ID)
$counts = null; $reviews = null; $avg = null;
try {
$counts = WC_Comments::get_rating_counts_for_product($product); // [1..5] => int
$reviews = (int) WC_Comments::get_review_count_for_product($product);
$avg = (string) WC_Comments::get_average_rating_for_product($product);
} catch (Throwable $e) {
// Fallback: licz z komentarzy
$comments = get_comments([
'post_id' => $id,
'status' => 'approve',
'meta_query' => [['key' => 'rating', 'compare' => 'EXISTS']],
'number' => 0,
]);
$buckets = [1=>0,2=>0,3=>0,4=>0,5=>0];
$sum = 0; $cnt = 0;
foreach ($comments as $c) {
$r = (int) get_comment_meta($c->comment_ID, 'rating', true);
if ($r >= 1 && $r <= 5) { $buckets[$r]++; $sum += $r; $cnt++; }
}
$counts = $buckets;
$reviews = $cnt;
$avg = $cnt ? number_format($sum / $cnt, 2, '.', '') : '0';
}
if ( $mode === 'run' ) {
update_post_meta($id, '_wc_rating_count', $counts);
update_post_meta($id, '_wc_review_count', $reviews);
update_post_meta($id, '_wc_average_rating', $avg);
if ( function_exists('wc_delete_product_transients') ) {
wc_delete_product_transients($id);
}
$fixed++;
}
if ($avg !== $before_avg || $reviews !== $before_rev || $counts !== $before_counts) {
$rows[] = sprintf(
'ID %d: avg %s → %s | reviews %d → %d | counts %s → %s',
$id,
($before_avg === '' ? '∅' : $before_avg),
$avg,
$before_rev,
$reviews,
json_encode($before_counts, JSON_UNESCAPED_UNICODE),
json_encode($counts, JSON_UNESCAPED_UNICODE)
);
}
}
// Wynik w admin notices
add_action('admin_notices', function () use ($mode, $rows, $fixed, $ids) {
$title = $mode === 'run' ? 'MF Fix Ratings — zapis zakończony' : 'MF Fix Ratings — podgląd (nic nie zapisano)';
$body = $rows ? ('<pre style="white-space:pre-wrap;max-height:400px;overflow:auto;">'
. esc_html(implode("\n", $rows)) . '</pre>')
: '<p>Brak rozjazdów do poprawy.</p>';
$foot = sprintf('<p>Produkty skanowane: %d. %s</p>',
count($ids),
$mode === 'run' ? 'Zaktualizowano: ' . intval($fixed) : 'Aby zapisać zmiany, uruchom: ?mf_fix_ratings=run'
);
echo '<div class="notice notice-success"><p><strong>' . esc_html($title) . '</strong></p>' . $body . $foot . '</div>';
});
});
Kod uruchamiamy na zapleczu: ?mf_fix_ratings=preview (podgląd) albo ?mf_fix_ratings=run (zapis).
Gdyby nadal problem pojawiał się w poszczególnych kategoriach można je sprawdzić indywidualnie i naprawić.
Wywołujemy wtedy polecenie za pomocą: ?mf_scan_cat=preview&cat=SLUG
lub: ?mf_scan_cat=run&cat=SLUG żeby uruchomić naprawę
add_action('init', function () {
if ( ! is_admin() || ! current_user_can('manage_woocommerce') ) return;
if ( empty($_GET['mf_scan_cat']) || empty($_GET['cat']) ) return;
// Uruchom po załadowaniu WooCommerce
if ( ! did_action('woocommerce_init') ) {
add_action('woocommerce_init', __FUNCTION__);
return;
}
$mode = sanitize_key($_GET['mf_scan_cat']); // preview | run
$slug = sanitize_title($_GET['cat']); // np. mauerabdeckung
// Pobierz ID produktów z tej kategorii
$ids = wc_get_products([
'status' => 'publish',
'limit' => -1,
'category' => [$slug],
'return' => 'ids',
]);
$rows = [];
$fixed = 0;
foreach ($ids as $id) {
$product = wc_get_product($id);
if ( ! $product ) continue;
// META przed
$m_avg = get_post_meta($id, '_wc_average_rating', true);
$m_rev = (int) get_post_meta($id, '_wc_review_count', true);
$m_counts = get_post_meta($id, '_wc_rating_count', true);
if ( ! is_array($m_counts) ) $m_counts = []; // bywa '[""]' lub ''
// Wylicz wprost z zatwierdzonych komentarzy
$comments = get_comments([
'post_id' => $id,
'status' => 'approve',
'meta_query' => [[ 'key'=>'rating', 'compare'=>'EXISTS' ]],
'number' => 0,
]);
$buckets = [1=>0,2=>0,3=>0,4=>0,5=>0];
$sum = 0; $cnt = 0;
foreach ($comments as $c) {
$r = (int) get_comment_meta($c->comment_ID, 'rating', true);
if ($r >= 1 && $r <= 5) { $buckets[$r]++; $sum += $r; $cnt++; }
}
$c_avg = $cnt ? number_format($sum / $cnt, 2, '.', '') : '0';
$diff = ($c_avg !== $m_avg) || ($cnt !== $m_rev) || ($buckets !== $m_counts);
if ( $diff && $mode === 'run' ) {
update_post_meta($id, '_wc_rating_count', $buckets);
update_post_meta($id, '_wc_review_count', $cnt);
update_post_meta($id, '_wc_average_rating', $c_avg);
if ( function_exists('wc_delete_product_transients') ) wc_delete_product_transients($id);
$fixed++;
}
if ( $diff ) {
$rows[] = sprintf(
'%s | ID %d | %s | AVG %s → %s | REV %d → %d | COUNTS %s → %s',
get_the_title($id),
$id,
get_permalink($id),
($m_avg === '' ? '∅' : $m_avg),
$c_avg,
$m_rev, $cnt,
json_encode($m_counts, JSON_UNESCAPED_UNICODE),
json_encode($buckets, JSON_UNESCAPED_UNICODE)
);
}
}
add_action('admin_notices', function () use ($mode, $rows, $fixed, $ids, $slug) {
$title = ($mode === 'run' ? 'MF Scan Cat — zapis zakończony' : 'MF Scan Cat — podgląd (bez zapisu)') . ' — ' . esc_html($slug);
$body = $rows ? ('<pre style="white-space:pre-wrap;max-height:450px;overflow:auto;">'. esc_html(implode("\n\n", $rows)) .'</pre>') : '<p>Brak rozjazdów.</p>';
$foot = sprintf('<p>Kategoria: %s | Przeskanowano: %d | %s</p>',
esc_html($slug),
count($ids),
$mode === 'run' ? 'Naprawiono: '.intval($fixed) : 'Aby zapisać: ?mf_scan_cat=run&cat='.urlencode($slug)
);
echo '<div class="notice notice-success"><p><strong>'. $title .'</strong></p>'. $body . $foot .'</div>';
});
}, 99);