Błąd zliczania RatingValue na stronach kategorii

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);
Przewijanie do góry